WellSpring

Istio-3주차 Istio Traffic 제어 및 복원력 본문

ISTIO

Istio-3주차 Istio Traffic 제어 및 복원력

daniel00324 2025. 4. 19. 18:22

목차

     

    ※ 본 게재 글은 gasida님의 'Istio' 스터디 강의 및 실습예제와 'Istio in Action' 서적 참고하여 작성하였습니다.


    [ 주요 참고 링크 ]

    [ 실습 환경 설정 ]

    1) k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)

    더보기
    #
    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 - <<EOF
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
    - role: control-plane
      extraPortMappings:
      - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
        hostPort: 30000
      - containerPort: 30001 # Prometheus
        hostPort: 30001
      - containerPort: 30002 # Grafana
        hostPort: 30002
      - containerPort: 30003 # Kiali
        hostPort: 30003
      - containerPort: 30004 # Tracing
        hostPort: 30004
      - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
        hostPort: 30005
      - containerPort: 30006 # TCP Route
        hostPort: 30006
      - containerPort: 30007 # kube-ops-view
        hostPort: 30007
      extraMounts: # 해당 부분 생략 가능
      - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
        containerPath: /istiobook
    networking:
      podSubnet: 10.10.0.0/16
      serviceSubnet: 10.200.1.0/24
    EOF
    
    # 설치 확인
    docker ps
    
    # 노드에 기본 툴 설치
    docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
    
    # (옵션) kube-ops-view
    helm repo add geek-cookbook 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

     

     

     

    2) istio 1.17.8 설치 (addon 필수) - Docs , Install , profile  

    더보기
    # 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 <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: netshoot
    spec:
      containers:
      - name: netshoot
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
    EOF

     

    [ 설치 결과 - 확인 ]

    1) istio-system 내 istiod 및 monitoring Pods 확인 ( k8s-control-plane 에서 수행 )

    2) istio enable 및 확인

     


    Chap5. 트레픽 제어 : 세밀한 트레픽 라우팅

    ☞ 고객에게 제공하는 서비스의 품질 유지를 위해 최소한의 중단과 영향으로 안전하게 새 버전을 도입하는 방법을 고민해 보자!!

     

    5.1 새로운 코드 배포의 위험 줄이기

     - ACME 사에서 시도한 패턴 중 블루/그린 배포 활용 : 여전히 v1 -> v2 전환 시 Big-bang을 겪어야 함

    더보기

     

     

    5.1.1 배포 vs 릴리스

    더보기

    [ 정의 ]

    a. 배포( Deployment ) : 어플리케이션의 새 버전(또는 설정 변경)을 EKS 클러스터에 실제로 반영하는 작업

     

    • 일반적으로는 GitOps 또는 CI/CD 파이프라인을 통해 이루어지며,
    • kubectl apply, helm upgrade, 혹은 ArgoCD, Flux와 같은 툴을 통해 수행됩니다.
    • 배포의 목적은 새로운 기능, 패치, 설정 등을 클러스터에 반영하는 것입니다.

     

     

    b. 릴리스 ( Release ) : 배포된 기능이나 버전을 실제로 사용자에게 노출시키는 단계

     ☞ 해당 시점부터 사용자 트래픽이 해당 버전을 타게 되며, Istio 같은 서비스 메시를 사용하면 트래픽을 버전별로 분리해서 릴리스 전략을 정교하게 구현할 수 있습니다.

     

    • Canary: 전체 트래픽 중 일부(예: 5%, 20%)만 새 버전으로 분기
    • Blue-Green: 두 버전을 동시에 띄우고 라우팅만 전환
    • Header 기반: 특정 조건(쿠키, 사용자, 지역)에 따라 라우팅

     

    참조 : Istio in Action ( 릴리스란 운영 환경 트래픽을 배포로 이관하는 순간을 말하며, 이상적으로 점진적으로 이뤄져야 한다.)

     

    실 트래픽은 구 버전이 대부분 처리하고, 신규 트래픽 일부를 신 버전에서 처리하는 데, 이런 방식을 Canarying  또는 Canary release 라고 부른다.

    5.2 이스티오로 요청 라우팅 하기

    ☞ 이전 Chap2 에서 트래픽 라우팅은 Istio Virtual Service 리소스를 사용함

    더보기
    • 2장에서는 Istio를 사용하여 카탈로그 서비스로의 트래픽을 제어했습니다.
    • Istio VirtualService 리소스를 사용하여 트래픽을 라우팅하는 방법을 지정했습니다.
    • 어떻게 작동하는지 자세히 살펴보겠습니다. 요청의 내용에 따라 헤더를 평가하여 요청의 경로를 제어합니다.
    • 이러한 방식으로 다크 런치라는 기술을 사용하여 특정 사용자에게 배포를 제공할 수 있습니다.
    • 어두운 실행에서는 많은 사용자가 알려진 작동 중인 서비스 버전으로 보내지며, 특정 클래스의 사용자는 최신 버전으로 보내집니다.
    • 따라서 우리는 다른 모든 사람에게 영향을 미치지 않고 특정 그룹에 통제된 방식으로 새로운 기능을 노출할 수 있습니다.

    5.2.1 작업 공간 청소

    # 4장 실습 리소스들 삭제
    kubectl delete deployment,svc,gateway,virtualservice,destinationrule --all -n istioinaction

     

    5.2.2 Deploying v1 of the catalog service

    더보기
    # 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   <none>           <none>
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    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
    
    # 외부 노출을 위해 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   95s
    
    NAME                                                    GATEWAYS              HOSTS                          AGE
    virtualservice.networking.istio.io/catalog-vs-from-gw   ["catalog-gateway"]   ["catalog.istioinaction.io"]   3s
    
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 3
    
    
    # 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
        "port": 80,        
        "protocol": "TCP",
        "targetPort": 8080 # 순서2
      },
      {
        "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

     

    [ 실행 결과 - 한 눈에 보기 ]

    1) istio 실습 POD 상태 확인

     

    2)  netshoot Pod 통한 catalog items 확인

     

    3) gw, svc 배포 확인

     

     


    • 호스트 외부에서 호출 경로 : 아래 그림 맨 왼쪽(curl client)는 외부라고 생각하면 됨.  

     

    • 상세 정보 확인

     

     

    #
    docker exec -it myk8s-control-plane istioctl proxy-status
    NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
    catalog-6cf4b97d-ftl77.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
    istio-ingressgateway-996bc6bb6-zvtdc.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
    
    # istio-ingressgateway
    ## LDS - Listener Discovery Service
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
    
    ## RDS - Route Discovery Service
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
    
    ## CDS - Cluseter Discovery Service
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system
    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
    
    ## EDS - Endpoint Discovery Service
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster '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||catalog.istioinaction.svc.cluster.local' -o json
    
    # 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

     

    • 특정 파드의 istio-proxy 에 Envoy 에 Admin 웹 접속
    # 신규 터미널 : istio-ingressgateway 파드
    kubectl port-forward deploy/istio-ingressgateway -n istio-system 15000:15000
    
    # 
    open http://127.0.0.1:15000

    5.2.3 Deploying v2 of the catalog service

    더보기

    ☞ istio 트래픽 제어 기능 동작을 알아보기 위해, catalog 서비스 v2 를 배포해보자.

        ⇒ 버전1,2 호출 된다. v2 호출되지 않게 할 수 없을까?

     

    # 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-ftl77        2/2     Running   0          43m   10.10.0.14   myk8s-control-plane   <none>           <none>
    catalog-v2-6df885b555-6hmcl   2/2     Running   0          13m   10.10.0.15   myk8s-control-plane   <none>           <none>
    
    docker exec -it myk8s-control-plane istioctl proxy-status
    NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
    catalog-6cf4b97d-ftl77.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
    catalog-v2-6df885b555-6hmcl.istioinaction             Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     1.17.8
    istio-ingressgateway-996bc6bb6-zvtdc.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-fl492     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",
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295,
                        "trackRemaining": true
                    }
                ]
            },
            "commonLbConfig": {
                "localityWeightedLbConfig": {}
            },
    ...
    
    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.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.17: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||catalog.istioinaction.svc.cluster.local' -o json
    ...
       {
            "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
            "addedViaApi": true,
            "hostStatuses": [
                {
                    "address": {
                        "socketAddress": {
                            "address": "10.10.0.14",
                            "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.15",
                            "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 도 직접 확인해보자
    Traffic Distribution, Traffic Animation 체크

     


    [ 실행 결과 - 한 눈에 보기 ]

     1) Catalog v2 배포후, deploy, POD, proxy status 확인

     

    2) Kiali 확인

     

    3) Destination Rule 배포

     

    4) FQDN 확인

     

    5) endpoint 확인

     

    6) V1으로 routing 변경한 VS 배포 후, kiali 관찰

     

    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 을 만들어준다.
    #
    kubectl get pod -l app=catalog -n istioinaction --show-labels
    NAME                          READY   STATUS    RESTARTS   AGE   LABELS
    catalog-6cf4b97d-ftl77        2/2     Running   0          56m   app=catalog,...,version=v1
    catalog-v2-6df885b555-6hmcl   2/2     Running   0          26m   app=catalog,...,version=v2
    
    #
    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
    
    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",
    ...
    
    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"
            },
    ...
    
    # 
    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
    
    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.16:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.17:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    
    
    
    # VirtualService 수정 (subset 추가)
    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
    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.16: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로 보내도록 하자.

    #
    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:
            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
    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"
    
    # (옵션) 신규 터미널 : 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 확인 - 위에서 부터 적용되는 순서 중요!
    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...          /*

     

     

    [ 헤더 규칙에 따른 routing 분기 확인 ]

    5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)

    더보기
    호출 그래프 내 깊은 위치에서 수행하는 특정 콘텐츠가 담긴 요청 라우팅

     

    • 지금까지 이스티오를 사용해 요청을 라우팅하는 방법을 살펴봤지만, 라우팅 수행 위치가 에지/게이트웨이뿐이었다.
    • 이런 트래픽 규칙은 호출 그래프 내 깊은 곳에서도 적용할 수 있습니다. (그림 5.9 참조).
    • 2장에서 이 작업을 수행했으므로, 프로세스를 다시 만들고 기대대로 동작하는지 확인해보자.
    # 초기화
    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           55m
    deployment.apps/catalog-v2   1/1     1            1           48m
    deployment.apps/webapp       1/1     1            1           42s
    
    NAME                              READY   STATUS    RESTARTS   AGE
    pod/catalog-6cf4b97d-jxpb8        2/2     Running   0          55m
    pod/catalog-v2-6df885b555-rg9f5   2/2     Running   0          48m
    pod/webapp-7685bcb84-2q7rg        2/2     Running   0          42s
    
    NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    service/catalog   ClusterIP   10.200.1.254   <none>        80/TCP    55m
    service/webapp    ClusterIP   10.200.1.61    <none>        80/TCP    42s
    
    NAME                ENDPOINTS                         AGE
    endpoints/catalog   10.10.0.16:3000,10.10.0.17:3000   55m
    endpoints/webapp    10.10.0.18:8080                   42s

     

    webapp 배포
    • GW, VS 설정 후 호출 테스트 : webapp → catalog 는 k8s service(clusterIP) 라우팅 사용
    # 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
    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",
    ...
    
    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.18:8080     HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
    
    
    # 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-18T13:27:57.178Z] "HEAD /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 0 8 8 "172.18.0.1" "curl/8.7.1" "8d425652-17a9-4b41-a21c-874acab3b1f4" "webapp.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:51809 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
    => 이 로그는 webapp 서비스의 사이드카 프록시가 클라이언트로부터 직접 HTTP 요청을 받은 장면이고, 이 요청을 10.10.0.18:8080 (즉, webapp 서비스의 실제 컨테이너)으로 보냄을 의미
    [2025-04-18T13:27:58.237Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "172.18.0.1" "beegoServer" "49b55b86-2505-4a5c-aadf-950d03705b87" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.18:45152 10.200.1.254:80 172.18.0.1:0 - default
    =>  이 로그는 webapp 서비스가 catalog 서비스로 HTTP 요청을 보낸 상황이에요. Envoy는 catalog.istioinaction이라는 Kubernetes catalog 서비스(clusterIp 10.200.1.254:80)로 라우팅하고, 실제 Pod IP는 10.10.0.16:3000으로 연결되었어요.
    ...

     

    [ VS 및 DR 재 배포 후, 라우팅 규칙 확인 ]

     

     

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

    카탈로그 서비스 v1으로 모든 트래픽을 보내는 virtual Service 와 DR 을 설정해보자.

     

    #
    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는 메시 내의 모든 사이드카(현재 webapp, catalog)에 적용된다. 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 라우팅 전달 처리! : 신규터미널
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    [2025-04-18T13:52:54.772Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "172.18.0.1" "beegoServer" "2035962f-144f-4d07-9102-4e3ab7ea3484" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80|version-v1|catalog.istioinaction.svc.cluster.local 10.10.0.18:52458 10.200.1.254:80 172.18.0.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.254" # 해당 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

     

     

    [ 실행 결과 - 한 눈에 보기 ]

     

     


    #
    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개 항목 확인
    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.254"
                    ],
                    "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
    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

     

    [ 실행 결과 - 한 눈에 보기 ]

     

    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%까지) 간단하다.
    • 이스티오로 가중치 기반의 트래픽 전환을 수행하는 방법을 살펴보자.
    # 이전 절부터 다음 서비스가 실행 중이다 : 파드에 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   STATUS    RESTARTS   AGE     LABELS
    catalog-6cf4b97d-q4xv5        2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v1,version=v1
    catalog-v2-6df885b555-df7g4   2/2     Running   0          19m     app=catalog,...,service.istio.io/canonical-revision=v2,version=v2
    webapp-7685bcb84-skzgg        2/2     Running   0          2m48s   app=webapp,...,service.istio.io/canonical-revision=latest
    
    # 반복 호출테스트 : 신규터미널
    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 로 라우팅해보자
    #
    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 호출 비중 확인
    for i in {1..10};  do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    
    # 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 반반으로 라우팅 해보자
    #
    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
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    
    # 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는 릴리스를 수행하는 데 필요한 작절한 설정을 모두 만든다.

     

    https://github.com/stefanprodan/gitops-istio
    • 이스티오와 Flagger를 함께 사용하는 방법을 살펴보자.
    • 이전 절에서는 catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 배포했다.

     

    Step1. 이 둘을 제거하고 Flagger가 라우팅과 배포 변경을 처리하게 하자.

    # catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 제거
    kubectl delete virtualservice catalog -n istioinaction
    kubectl delete deploy catalog-v2 -n istioinaction
    kubectl delete service catalog -n istioinaction
    kubectl delete destinationrule catalog -n istioinaction
    
    # 남은 리소스 확인
    kubectl get deploy,svc,ep -n istioinaction
    NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/catalog   1/1     1            1           77m
    deployment.apps/webapp    1/1     1            1           78m
    
    NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
    service/webapp   ClusterIP   10.200.1.73   <none>        80/TCP    78m
    
    NAME               ENDPOINTS         AGE
    endpoints/webapp   10.10.0.19:8080   78m
    
    kubectl get gw,vs -n istioinaction
    NAME                                            AGE
    gateway.networking.istio.io/coolstore-gateway   73m
    
    NAME                                                       GATEWAYS                HOSTS                         AGE
    virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   73m

     

    • Flagger는 서비스 상태를 판단할 때 메트릭에 의존하며, 카나리 릴리스를 사용할 때 특히 그렇다.
    • Flagger가 성공 메트릭을 사용하려면 프로메테우스를 설치해 이스티오 데이터 플레인수집해야 한다.
    • 이 책의 예제를 따라오고 있다면, 프로메테우스 샘플이 이미 설치돼 있을 것이다.
    https://docs.flagger.app/

     

    Step2.  다음으로는 Flagger를 설치하려고 한다.

         helm 사용. https://docs.flagger.app/install/flagger-install-on-kubernetes

    # CRD 설치 
    kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
    kubectl get crd | grep flagger
    alertproviders.flagger.app                 2025-04-19T03:11:50Z
    canaries.flagger.app                       2025-04-19T03:11:50Z
    metrictemplates.flagger.app                2025-04-19T03:11:50Z
    
    # Helm 설치
    helm repo add flagger https://flagger.app
    helm install flagger flagger/flagger \
      --namespace=istio-system \
      --set crd.create=false \
      --set meshProvider=istio \
      --set metricServer=http://prometheus:9090
    
    # 디플로이먼트 flagger 에 의해 배포된 파드 확인
    kubectl get pod -n istio-system -l app.kubernetes.io/name=flagger
    NAME                       READY   STATUS    RESTARTS   AGE
    flagger-6d4ffc5576-q78ls   1/1     Running   0          2m54s
    
    # 시크릿
    kubectl get secret -n istio-system | grep flagger-token
    flagger-token-v2f5z                                kubernetes.io/service-account-token   3      4m11s
    
    # 시크릿 확인 : ca.crt 는 k8s 루프 인증서
    kubectl view-secret -n istio-system flagger-token-v2f5z --all
    ca.crt='-----BEGIN CERTIFICATE-----
    MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
    cm5ldGVzMB4XDTI1MDQxOTAxNDEyMVoXDTM1MDQxNzAxNDEyMVowFTETMBEGA1UE
    AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzN
    onEbSSXBHfHhJICwREU4EX4D0K2Bwg7SXNwZNl3QwwPOpjFoaRbr6Hdog88jmo8A
    Mo/RDKDj+Lvr0FE3hBvm5igLndWgnjYqpIHfDq31AYvWCoJvbBQ/omDIal4u1HHI
    8XNqEpxl3JhsV9M9pMEx2+Gvlp1og8qjbB3B5amutriNQom6VOG0HBzJQuvNG8op
    2GhWD4IOQf3vfKltGE9Y/KzbBLajtPueMkHZr/kH4Qy/Xu9kSGc8lhdsxrRSqoTX
    ytyr2rOe83vliKhGKYtkiWESIm35BcVF1rp+jl0nLGs8IMhmR5Ll9A9pZ5xsqFzN
    ndg7DjpdcKKwCxzw9BsCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
    /wQFMAMBAf8wHQYDVR0OBBYEFPm+n2+NblRv8ZWaoVW4fMvmFzuNMBUGA1UdEQQO
    MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAFkDbnnWS+9u9AKnlih0
    Cltedk01oId5I31SWzvfI1owgudBH6pGxs3dGeij5iNDlV2StDzG5mqUIW6Y5iXR
    hVMUUl/GvscaoSqFb5cIEgfmzdDSsNm1eBON8HpoN4VuEyuRZn1O39JAYIzVQcwD
    LgO/dTCZwqM6hs6LC2Qx/PlxlQLt3agT3sZWXkztbOjLpLCuqVrz0NIRzFS3M2hA
    b1+ACPllYGIRiEpSNxzbybgjui4W8bt8W2AjTPuqXIB/TuEcQrgAzrVcQsVf2XID
    nC+WEpmUURKxQ51dLpLLQgFfojz+LXrdrlGJ4hpcfj0aN5j221+rjHTnI6PrsQdT
    qME=
    -----END CERTIFICATE-----
    '
    namespace='istio-system'
    token='eyJhbGciOiJSUzI1NiIsImtpZCI6InFIUnV5blBfSUVDaDQ0MUxyOXR2MFRqV1g5ekVjaU1wdWZvSDFXZXl6Z3cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJpc3Rpby1zeXN0ZW0iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZmxhZ2dlci10b2tlbi12MmY1eiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJmbGFnZ2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNWIxZWM4MjUtODU4My00OGViLWI4MGMtNmYyNzEzZTBlMzA3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmlzdGlvLXN5c3RlbTpmbGFnZ2VyIn0.Eb14h5EKU9FfYZa3XkydrFFYUSk4gPYUM0j76Cbsb4XTtAL0U54-RfMiNcX5rfyK6WFOUhU5W6yuAChRhsl7TEzZCpgj3aVRNNe5TRsy-mYpG89FfBSpU0H6wmZyJnvHDcweo1eh-BLIThH6-_1GuUeJDc18WsapllkcHNIXiR_7gudgY7tfn29KoKxlv72K_HPYIerIsTGZe9tHr7K__lvl0Yz779yKNXKUlSerqho0-z2cPsmhFRR1KvPwrhi6UQck70s_snMlaecVJvkrYXCnEvsMkUwpaa6JmDmamKC3NNm9zWJYKtEt0fHHomZoJQFHHQCiYDTVkjYi8ErE2A'
    
    # token 을 jtw.io 에서 Decoded 확인
    {
      "iss": "kubernetes/serviceaccount",
      "kubernetes.io/serviceaccount/namespace": "istio-system",
      "kubernetes.io/serviceaccount/secret.name": "flagger-token-v2f5z",
      "kubernetes.io/serviceaccount/service-account.name": "flagger",
      "kubernetes.io/serviceaccount/service-account.uid": "5b1ec825-8583-48eb-b80c-6f2713e0e307",
      "sub": "system:serviceaccount:istio-system:flagger"
    }

     

    Step3. 아래 코드 처럼 Flagger Canara 리소스를 사용해 카나리 릴리스의 파라미터를 지정하자.

                Flagger가 적절한 리로스를 만들어 이 릴리스를 주관하게 할 것이다. - Docs

    #  cat ch5/flagger/catalog-release.yaml        
    apiVersion: flagger.app/v1beta1
    kind: Canary
    metadata:
      name: catalog-release
      namespace: istioinaction
    spec:   
      targetRef: #1 카나리 대상 디플로이먼트 https://docs.flagger.app/usage/how-it-works#canary-target
        apiVersion: apps/v1
        kind: Deployment
        name: catalog  
      progressDeadlineSeconds: 60
      # Service / VirtualService Config
      service: #2 서비스용 설정 https://docs.flagger.app/usage/how-it-works#canary-service
        name: catalog
        port: 80
        targetPort: 3000
        gateways:
        - mesh    
        hosts:
        - catalog
      analysis: #3 카니리 진행 파라미터 https://docs.flagger.app/usage/how-it-works#canary-analysis
        interval: 45s
        threshold: 5
        maxWeight: 50
        stepWeight: 10
        match: 
        - sourceLabels:
            app: webapp
        metrics: # https://docs.flagger.app/usage/metrics , https://docs.flagger.app/faq#metrics
        - name: request-success-rate # built-in metric 요청 성공률
          thresholdRange:
            min: 99
          interval: 1m
        - name: request-duration # built-in metric 요청 시간
          thresholdRange:
            max: 500
          interval: 30s

     

    • 이 Canary 리소스에서는 어떤 쿠버네티스 Deployment가 카나리 대상인지, 어떤 쿠버네티스 Service와 이스티오 VirtualService가 자동으로 만들어져야 하는지, 카나리를 어떻게 진행해야 하는지 등을 지정한다.
    • Canary 리소스의 마지막 부분은 카나리를 얼마나 빨리 진행할지, 생존을 판단하기 위해 지켜볼 메트릭은 무엇인지, 성공을 판단할 임계값은 얼마인지를 기술하고 있다.
    • 45초마다 카나리의 각 단계를 평가하고, 단계별로 트래픽을 10%씩 늘린다. 트래픽이 **50%에 도달하면 100%**로 바꾼다.
    • 성공률 메트릭의 경우 1분 동안의 성공률이 99% 이상이어야 한다. 또한 P99(상위 99%) 요청 시간은 500ms까지 허용한다.
    • 이 메트릭들이 연속으로 5회를 초과해 지정한 범위와 다르면, 롤백한다.

     Step4. 위 설정을 적용하고 catalog 서비스를 자동으로 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
    
    
    # flagger (operator) 가 catalog를 위한 canary 배포환경을 구성
    kubectl apply -f ch5/flagger/catalog-release.yaml -n istioinaction
    
    # flagger 로그 확인 : Service, Deployment, VirtualService 등을 설치하는 것을 확인할 수 있습니다.
    kubectl logs -f deploy/flagger -n istio-system
    ...
    
    # 확인
    kubectl get canary -n istioinaction -w
    NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
    catalog-release   Initializing  0        2025-04-19T05:10:00Z
    catalog-release   Initialized   0        2025-04-19T05:15:54Z
    
    kubectl get canary -n istioinaction -owide
    NAME              STATUS        WEIGHT   SUSPENDED   FAILEDCHECKS   INTERVAL   MIRROR   STEPWEIGHT   STEPWEIGHTS   MAXWEIGHT   LASTTRANSITIONTIME
    catalog-release   Initialized   0                    0              45s                 10                         50          2025-04-19T05:15:54Z
    
    # flagger Initialized 동작 확인
    ## catalog-primary deployment/service 가 생성되어 있음, 기존 catalog deploy/service 는 파드가 0으로 됨
    kubectl get deploy,svc,ep -n istioinaction -o wide
    
    NAME                              READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                         SELECTOR
    deployment.apps/catalog           0/0     0            0           3h34m   catalog      istioinaction/catalog:latest   app=catalog,version=v1
    deployment.apps/catalog-primary   1/1     1            1           6m41s   catalog      istioinaction/catalog:latest   app=catalog-primary
    deployment.apps/webapp            1/1     1            1           3h36m   webapp       istioinaction/webapp:latest    app=webapp
    
    NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
    service/catalog           ClusterIP   10.200.1.168   <none>        80/TCP    5m56s   app=catalog-primary
    service/catalog-canary    ClusterIP   10.200.1.242   <none>        80/TCP    6m41s   app=catalog
    service/catalog-primary   ClusterIP   10.200.1.119   <none>        80/TCP    6m41s   app=catalog-primary
    service/webapp            ClusterIP   10.200.1.73    <none>        80/TCP    3h36m   app=webapp
    
    NAME                        ENDPOINTS         AGE
    endpoints/catalog           10.10.0.23:3000   5m56s
    endpoints/catalog-canary    <none>            6m41s
    endpoints/catalog-primary   10.10.0.23:3000   6m41s
    endpoints/webapp            10.10.0.19:8080   3h36m
    
    ## VS catalog 생성되었음
    kubectl get gw,vs -n istioinaction
    NAME                                            AGE
    gateway.networking.istio.io/coolstore-gateway   137m
    
    NAME                                                       GATEWAYS                HOSTS                         AGE
    virtualservice.networking.istio.io/catalog                 ["mesh"]                ["catalog"]                   8m17s
    virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   137m
    
    ## VS catalog 확인
    kubectl get vs -n istioinaction catalog -o yaml | kubectl neat
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      annotations:
        helm.toolkit.fluxcd.io/driftDetection: disabled
        kustomize.toolkit.fluxcd.io/reconcile: disabled
      name: catalog
      namespace: istioinaction
    spec:
      gateways:
      - mesh
      hosts:
      - catalog
      http:
      - match:
        - sourceLabels:
            app: webapp
        route:
        - destination:
            host: catalog-primary
          weight: 100
        - destination:
            host: catalog-canary
          weight: 0
      - route:
        - destination:
            host: catalog-primary
          weight: 100
    
    # destinationrule 확인
    kubectl get destinationrule -n istioinaction
    NAME              HOST              AGE
    catalog-canary    catalog-canary    15m
    catalog-primary   catalog-primary   15m
    
    kubectl get destinationrule -n istioinaction catalog-primary -o yaml | kubectl neat
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: catalog-primary
      namespace: istioinaction
    spec:
      host: catalog-primary
    
    kubectl get destinationrule -n istioinaction catalog-canary -o yaml | kubectl neat
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: catalog-canary
      namespace: istioinaction
    spec:
      host: catalog-canary
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 
    NAME     DOMAINS                                                        MATCH     VIRTUAL SERVICE
    80       catalog-canary, catalog-canary.istioinaction + 1 more...       /*        
    80       catalog-primary, catalog-primary.istioinaction + 1 more...     /*        
    80       catalog, catalog.istioinaction + 1 more...                     /*        catalog.istioinaction
    ...
    
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | egrep 'RULE|catalog'
    SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
    catalog-canary.istioinaction.svc.cluster.local          80        -          outbound      EDS              catalog-canary.istioinaction
    catalog-primary.istioinaction.svc.cluster.local         80        -          outbound      EDS              catalog-primary.istioinaction
    catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS 
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local -o json
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-primary.istioinaction.svc.cluster.local -o json
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog-canary.istioinaction.svc.cluster.local -o json
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
    10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog-primary.istioinaction.svc.cluster.local
    10.10.0.23:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    
    # 해당 EDS에 메트릭 통계 값 0.
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
    ...
    
    # 현재 EDS primary 에 메트릭 통계 값 출력 중. 해당 EDS 호출.
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction --cluster 'outbound|80||catalog-primary.istioinaction.svc.cluster.local' -o json
    ...

    위 VS catalog 등 설정을 보면, catalog 서비스로 향하는 트래픽이 catalog-primary 서비스로는100%, catalog-canary 로는 0% 라우팅될 것임을 알 수 있다.

     

    [ 실행 결과 - 한 눈에 보기 ]


    • 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않았다.
    • Flagger는 원본 디폴로이먼트 대상(여기서는 catalog 디플로이먼트 ?catalog-primary가 아닌가?)의 변경 사항을 지켜보고, 카나리 디폴로이먼트(catalog-canary) 및 서비스(catalog-canary)를 생성하고, VirtualService 의 가중치를 조정한다.

    이제 catalog v2 를 도입하고 Flagger가 어떻게 릴리스에서 이를 자동화하는지, 어떻게 메트릭에 기반해 의사결정을 내리는지 살펴보자.

     

    https://docs.flagger.app/usage/deployment-strategies#canary-release

     

    https://docs.flagger.app/tutorials/istio-progressive-delivery

     

    • 또한 Flagger가 정상 메트릭 기준선을 가져올 수 있도록 이스티오를 통해 서비스에 대한 부하를 만들어보자.
      • 카나리는 Carary 오브젝트에 설정한 대로 45초마다 진행될 것이다.
      • 트래픽의 50%가 카나리로 이동할 때까지는 단계별로 10%씩 증가한다.
      • flagger가 메트릭에 문제가 없고 기준과 차이가 없다고 판단되면, 모든 트래픽이 카나리로 이동해 카나리가 기본 서비스로 승격 될 때까지 카나리가 진행된다.
      • 만약 문제가 발생하면 flagger는 자동으로 카나리 릴리스를 롤백할 것이다.
    # 반복 호출테스트 : 신규터미널1 - 부하 만들기
    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
    
    # flagger 로그 확인 : 신규터미널2
    kubectl logs -f deploy/flagger -n istio-system
    {"level":"info","ts":"2025-04-19T05:15:54.453Z","caller":"controller/events.go:33","msg":"Initialization done! catalog-release.istioinaction","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:09:09.442Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:09:09.444Z","caller":"controller/events.go:33","msg":"New revision detected! Scaling up catalog.istioinaction","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:09:54.441Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:09:54.446Z","caller":"controller/events.go:33","msg":"Starting canary analysis for catalog.istioinaction","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:09:54.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 10","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:10:39.443Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:10:39.469Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 20","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:11:24.437Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:11:24.461Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 30","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:12:09.445Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:12:09.472Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 40","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:12:54.429Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:12:54.445Z","caller":"controller/events.go:33","msg":"Advance catalog-release.istioinaction canary weight 50","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:13:39.444Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:13:39.453Z","caller":"controller/events.go:33","msg":"Copying catalog.istioinaction template spec to catalog-primary.istioinaction","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:14:24.438Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:14:24.440Z","caller":"controller/events.go:33","msg":"Routing all traffic to primary","canary":"catalog-release.istioinaction"}
    {"level":"info","ts":"2025-04-19T06:15:09.436Z","caller":"router/istio.go:414","msg":"Canary catalog-release.istioinaction uses HTTP service"}
    {"level":"info","ts":"2025-04-19T06:15:09.642Z","caller":"controller/events.go:33","msg":"Promotion completed! Scaling down catalog.istioinaction","canary":"catalog-release.istioinaction"}
    
    # flagger 상태 확인 : 신규터미널3
    ## 카나리는 Carary 오브젝트에 설정한 대로 45초마다 진행될 것이다.
    ## 트래픽의 50%가 카나리로 이동할 때까지는 단계별로 10%씩 증가한다.
    ## flagger가 메트릭에 문제가 없고 기준과 차이가 없다고 판단되면, 모든 트래픽이 카나리로 이동해 카나리가 기본 서비스로 승격 될 때까지 카나리가 진행된다.
    ## 만약 문제가 발생하면 flagger는 자동으로 카나리 릴리스를 롤백할 것이다.
    kubectl get canary -n istioinaction -w
    NAME              STATUS        WEIGHT   LASTTRANSITIONTIME
    catalog-release   Initialized   0        2025-04-19T05:15:54Z
    catalog-release   Progressing   0        2025-04-19T06:09:09Z
    catalog-release   Progressing   10       2025-04-19T06:09:54Z # 45초 간격
    catalog-release   Progressing   20       2025-04-19T06:10:39Z
    catalog-release   Progressing   30       2025-04-19T06:11:24Z
    catalog-release   Progressing   40       2025-04-19T06:12:09Z
    catalog-release   Progressing   50       2025-04-19T06:12:54Z
    catalog-release   Promoting     0        2025-04-19T06:13:39Z
    catalog-release   Finalising    0        2025-04-19T06:14:24Z
    catalog-release   Succeeded     0        2025-04-19T06:15:09Z
    
    
    # imageUrl 출력 (v2)을 포함하는 catalog deployment v2 배포
    cat ch5/flagger/catalog-deployment-v2.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: catalog
        version: v1
      name: catalog
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: catalog
          version: v1
      template:
        metadata:
          labels:
            app: catalog
            version: v1
        spec:
          containers:
          - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: SHOW_IMAGE
              value: "true"
            image: istioinaction/catalog:latest
            imagePullPolicy: IfNotPresent
            name: catalog
            ports:
            - containerPort: 3000
              name: http
              protocol: TCP
            securityContext:
              privileged: false
    
    kubectl apply -f ch5/flagger/catalog-deployment-v2.yaml -n istioinaction
    	kubectl get vs -n istioinaction catalog -o yaml -w # catalog vs 에 가중치 변경 모니터링
    	---
        route:
        - destination:
            host: catalog-primary
          weight: 90
        - destination:
            host: catalog-canary
          weight: 10
      - route:
        - destination:
            host: catalog-primary
          weight: 90
    ---
        route:
        - destination:
            host: catalog-primary
          weight: 80
        - destination:
            host: catalog-canary
          weight: 20
      - route:
        - destination:
            host: catalog-primary
          weight: 80
    ---
        route:
        - destination:
            host: catalog-primary
          weight: 70
        - destination:
            host: catalog-canary
          weight: 30
      - route:
        - destination:
            host: catalog-primary
          weight: 70
    ---
        route:
        - destination:
            host: catalog-primary
          weight: 60
        - destination:
            host: catalog-canary
          weight: 40
      - route:
        - destination:
            host: catalog-primary
          weight: 60
    ---
        route:
        - destination:
            host: catalog-primary
          weight: 50
        - destination:
            host: catalog-canary
          weight: 50
      - route:
        - destination:
            host: catalog-primary
          weight: 50
    ---
        route:
        - destination:
            host: catalog-primary
          weight: 100
        - destination:
            host: catalog-canary
          weight: 0
      - route:
        - destination:
            host: catalog-primary
          weight: 100
    ---
     
    # canary CRD 이벤트 확인
    kubectl describe canary -n istioinaction catalog-release | grep Events: -A20
    Events:
      Type    Reason  Age    From     Message
      ----    ------  ----   ----     -------
      Normal  Synced  10m    flagger  New revision detected! Scaling up catalog.istioinaction
      Normal  Synced  9m54s  flagger  Starting canary analysis for catalog.istioinaction
      Normal  Synced  9m54s  flagger  Advance catalog-release.istioinaction canary weight 10
      Normal  Synced  9m9s   flagger  Advance catalog-release.istioinaction canary weight 20
      Normal  Synced  8m24s  flagger  Advance catalog-release.istioinaction canary weight 30
      Normal  Synced  7m39s  flagger  Advance catalog-release.istioinaction canary weight 40
      Normal  Synced  6m54s  flagger  Advance catalog-release.istioinaction canary weight 50
      Normal  Synced  6m9s   flagger  Copying catalog.istioinaction template spec to catalog-primary.istioinaction
      Normal  Synced  5m24s  flagger  Routing all traffic to primary
      Normal  Synced  4m39s  flagger  (combined from similar events): Promotion completed! Scaling down catalog.istioinaction
    
    # 최종 v2 접속 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
         100

     

    ☞ 카나리 과정 중 kiali 확인

     

    ☞  프로메테우스에서 확인 : flagger_canary_weight - Link

     

    flagger_canary_metric_analysis{metric="request-duration"}

     

    flagger_canary_metric_analysis{metric="request-success-rate"}

    • Flagger를 사용해 이스티오의 API로 카나리 릴리스를 자동 제어함으로써, 리소스를 직접 설정하는 등 설정 오류를 일으킬 수 있는 수작업의 필요성을 없앴다.
    • 또한 Flagger는 다크 런치 테스트, 트래픽 미러링(다음 절에서 설명한다) 등도 할 수 있다.
    • 관련 내용은 웹 사이트를 참고하자. https://docs.flagger.app/usage/deployment-strategies

    [ 실습 결과 - 한눈에 보기 ]


    [ 실습 환경 정리 ]

     

    ☞ 실습을 정리하고 이 장을 계속 진행할 수 있는 상태로 만들기 위해 Flagger Canary 리소스를 제거한다.

    # Canary 삭제 : Flagger가 만든 service (catalog, catalog-canary, catalog-primary), destinationrule (catalog-canary, catalog-primary), deployment (catalog-primary) 를 제거함
    kubectl delete canary catalog-release -n istioinaction
    
    # catalog 삭제
    kubectl delete deploy catalog -n istioinaction
    
    # Flagger 삭제
    helm uninstall flagger -n istio-system

    5.4 Reducing risk even further: Traffic mirroring

    트래픽 미러링 소개

    더보기
    • 앞서 살펴본 요청 수준 라우팅과 트래픽 전환이라는 두 가지 기술을 사용하면 릴리스의 위험성을 낮출 수 있다.
    • 두 기술 모두 라이브 트래픽과 요청을 사용하므로, 영향의 파급 범위를 아무리 제어하더라도 사용자에게 영향을 줄 수 있었다.
    • 또 다른 접근법은 운영 환경 트래픽을 새 디플로이먼트로 미러링하는 것으로, 그림 5.10처럼 운영 환경 트래픽을 복사해 고객 트래픽 범위 외부의 새 디플로이먼트로 보내는 것이다.
    요청 경로 외 대역의 catalog-v2 서비스로 미러링된 트래픽

     

    • 미러링 방식을 사용하면, 실제 운영 환경 트래픽을 배포로 보냄으로써 사용자에게 영향을 주지 않고 새 코드가 어떻게 동작할지에 대한 실제 피드백을 얻을 수 있다.
    • 이스티오는 트래픽 미러링을 지원하며, 이는 배포 및 릴리스 수행의 위험성을 다른 두 방식보다 휠씬 더 줄일 수 있다. 한번 살펴보자.

    패킷 미러링은 서비스 레벨에서 HTTP 트래픽을 ‘실시간 복제’해 문제 없는지를 검증하는 데 탁월하고,
    패킷 복제는 네트워크 레벨에서 모든 패킷을 떼어내 깊이 분석하거나 보안 장비에 뿌리기에 알맞다.


    구분 패킷 미러링 (Traffic Mirroring) 패킷 복제 (Packet Replication)
    정의 원본 트래픽을 타깃 서비스로 ‘사이드카’처럼 복사하여 보내고, 응답은 무시 원본 패킷을 네트워크 레벨에서 그대로 복사해 보낸 후, 별도 처리 경로로 전달
    동작 계층 L7 (HTTP/gRPC) L3/L4 (TCP/UDP)
    주요 용도 새로운 버전 서비스의 실시간 테스트, A/B 테스트 지원 네트워크 모니터링·패킷 분석, 보안 인라인 장비 연동
    응답 처리 미러 대상의 응답은 호출 체인에 포함되지 않음 복제 대상의 응답도 원본과 별개로 처리하거나 폐기 가능
    구성 방법 VirtualService 내 mirror 필드 설정 Envoy Filter 또는 Sidecar 리소스로 TCP 프록시 필터 추가
    성능 영향 L7 처리 오버헤드 발생 (HTTP 헤더 파싱 등) 패킷 복제 비용, 네트워크 대역폭 증가
    프로토콜 지원 HTTP, HTTP/2, gRPC TCP, UDP
    레이턴시 영향 원본 호출 경로 영향 적음 (비동기 전송) 약간의 전송 지연 발생 가능 (패킷 복제 및 전송)
    보안 고려사항 미러링된 요청에 민감 데이터 포함 시 마스킹 필요 복제된 패킷 암호화·격리 채널 필요
    예시 설정 yaml<br>spec:<br> http:<br> - route: ...<br> mirror: &lt;subset&gt;<br> mirrorPercentage: 100 yaml<br>apiVersion: networking.istio.io/v1alpha3<br>kind: EnvoyFilter<br>...<br>tcpProxy:<br> statPrefix: tcp_replica<br> cluster: replica_service

     

    실습 환경 초기화 (실습)

    더보기
    # 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
    kubectl get svc,ep -n istioinaction -owide
    kubectl get gw,vs -n istioinaction
    
    # 반복 접속
    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

     

    [ 실습 결과 - 한 눈에 보기 ]

    트래픽 미러링 (실습)

    더보기

    step1. 미러링 수행이 필요한 VS 확인

    # cat ch5/catalog-vs-v2-mirror.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: 100
        mirror:
          host: catalog
          subset: version-v2
    • 이 VS는 라이브 트래픽을 전부 catalog(v1)으로 보내지만, 동시에 v2로도 미러링한다.
    • 미러링은 요청의 복사복을 만들어 미러링된 클러스터(여기서는 catalog-v2)로 전송하는 이른바 ‘보내고 잊는 방식’으로 수행된다.
    • 미러링된 요청은 실제 요청에는 영향을 줄 수 없는데, 미러링을 수행하는 이스티오 프록시미러링된 클러스터에서 오는 응답을 모두(성공인든, 실패든) 무시해버리기 때문이다.

     Step2. VS 리소스 생성

    # 반복 접속
    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; don
    
    # catalog istio-proxy 로그 활성화
    cat << EOF | kubectl apply -f -
    apiVersion: telemetry.istio.io/v1alpha1
    kind: Telemetry
    metadata:
      name: catalog
      namespace: istioinaction
    spec:
      accessLogging:
      - disabled: false
        providers:
        - name: envoy
      selector:
        matchLabels:
          app: catalog
    EOF
    
    kubectl get telemetries -n istioinaction
    NAME      AGE
    catalog   16s
    webapp    5h49m
    
    # istio-proxy 로그 확인 : 신규 터미널
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
    kubectl logs -n istioinaction -l version=v1 -c istio-proxy -f
    kubectl logs -n istioinaction -l app=catalog -l version=v2 -c istio-proxy -f
    혹은
    kubectl stern -n istioinaction -l app=catalog -c istio-proxy
    
    # proxy-config : webapp
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | grep catalog 
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction | grep catalog
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
    
    # proxy-config : catalog
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog 
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction | grep catalog 
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction | grep catalog 
    
    
    # 미러링 VS 설정
    kubectl apply -f ch5/catalog-vs-v2-mirror.yaml -n istioinaction
    
    # v1 으로만 호출 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
           0
    
    # v1 app 로그 확인
    kubectl logs -n istioinaction -l app=catalog -l version=v1 -c catalog -f
    request path: /items
    blowups: {}
    number of blowups: 0
    GET catalog.istioinaction:80 /items 200 502 - 0.375 ms
    GET /items 200 0.375 ms - 502
    ...
    
    # v2 app 로그 확인 : 미러링된 트래픽이 catalog v2로 향할때, Host 헤더가 수정돼 미러링/섀도잉된 트래픽임을 나타낸다.
    ## 따라서 Host:catalog:8080 대신 Host:catalog-shadow:8080이 된다.
    ## -shadow 접미사가 붙은 요청을 받는 서비스는 그 요청이 미러링된 요청임을 식별할 수 있어, 요청을 처리할 때 고려할 수 있다
    ## 예를 들어, 응답이 버려질 테니 트랜잭션을 롤백하지 않거나 리소스를 많이 사용하는 호출을 하지 않는 것 등.
    kubectl logs -n istioinaction -l app=catalog -l version=v2 -c catalog -f
    request path: /items
    blowups: {}
    number of blowups: 0
    GET catalog.istioinaction-shadow:80 /items 200 698 - 0.503 ms
    GET /items 200 0.503 ms - 698
    
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
    cat webapp-routes.json
    ...
                            "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,
                                    "retryHostPredicate": [
                                        {
                                            "name": "envoy.retry_host_predicates.previous_hosts",
                                            "typedConfig": {
                                                "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                            }
                                        }
                                    ],
                                    "hostSelectionRetryMaxAttempts": "5",
                                    "retriableStatusCodes": [
                                        503
                                    ]
                                },
                                "requestMirrorPolicies": [
                                    {
                                        "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                        "runtimeFraction": {
                                            "defaultValue": {
                                                "numerator": 100
                                            }
                                        },
                                        "traceSampled": false
    ...
    
    
    # 위 webapp과 상동 : 그거슨 mesh(gateway)이니까...
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json > webapp-routes.json
    cat catalog-routes.json
    ...

     

     

    [ 실행 결과 - 한 눈에 보기 ]

    1) envoy - matchlabel 적용한 yaml 적용

     

    2) 로그 확인

     

    3) 미러링 yaml 적용 및 v2 로그 확인

     

     4) 라우팅 정보를 통한 미러링 패킷 정보 확인


    Step3. (심화) webapp 과 catalog v2 파드에서 패킷 덤프로 확인

    # Istio 메시 내부망에서 모든 mTLS 통신 기능 끄기 설정 : (참고) 특정 네임스페이스 등 세부 조절 설정 가능 
    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: istio-system
    spec:
      mtls:
        mode: DISABLE
    EOF
    
    kubectl get PeerAuthentication -n istio-system
    NAME      MODE      AGE
    default   DISABLE   6h13m
    
    
    --------------------------------------------------------------------------------
    # catalog v2 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
    ## catalog v2 파드 IP 확인
    C2IP=$(kubectl get pod -n istioinaction -l app=catalog -l version=v2 -o jsonpath='{.items[*].status.podIP}')
    echo $C2IP
    
    ## veth 이름 확인
    docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }'
    C2VETH=$(docker exec -it myk8s-control-plane ip -c route | grep $C2IP | awk '{ print $3 }')
    echo $C2VETH
    veth853288c7 <- 해당 이름을 메모해두기
    --------------------------------------------------------------------------------
    
    # ngrep 확인(메모 정보 직접 기입!) : catalog v2 파드 tcp 3000
    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d veth4ede8164 '' 'tcp port 3000'"
    ## 요청
    T 2025/04/19 08:13:56.766981 10.10.0.19:49446 -> 10.10.0.27:3000 [AP] #19
    GET /items HTTP/1.1.
    host: catalog.istioinaction-shadow:80.
    user-agent: beegoServer.
    x-envoy-attempt-count: 1.
    x-forwarded-for: 172.18.0.1,10.10.0.19.
    x-forwarded-proto: http.
    x-request-id: 769c8e1f-2a2a-4498-b98d-0683c7c46281.
    accept-encoding: gzip.
    x-envoy-internal: true.
    x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
    x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
    x-b3-traceid: 8a650108ee32974ad419ff2948d9f8f2.
    x-b3-spanid: 12c05a3f651b36fa.
    x-b3-parentspanid: 2c77705aee579141.
    x-b3-sampled: 0.
    .
    
    ## 응답
    T 2025/04/19 08:13:56.770458 10.10.0.27:3000 -> 10.10.0.19:49446 [AP] #20
    HTTP/1.1 200 OK.
    x-powered-by: Express.
    vary: Origin, Accept-Encoding.
    access-control-allow-credentials: true.
    cache-control: no-cache.
    pragma: no-cache.
    expires: -1.
    content-type: application/json; charset=utf-8.
    content-length: 698.
    etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
    date: Sat, 19 Apr 2025 08:13:56 GMT.
    x-envoy-upstream-service-time: 2.
    x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
    x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
    server: istio-envoy.
    .
    [
      {
        "id": 1,
        "color": "amber",
        "department": "Eyewear",
        "name": "Elinor Glasses",
        "price": "282.00",
        "imageUrl": "http://lorempixel.com/640/480"
      },
    ...

     

    • 미러링 대상 서버는 응답을 안하는게 좋지만, 만약 응답을 webapp 파드에 한다 해도, webapp은 받고 나서 무시(drop?) 처리함.
    --------------------------------------------------------------------------------
    # webapp 파드의 vnic 와 vritual-pair 인 control-plane 노드(?)의 veth 이름 찾기
    ## webapp 파드 IP 확인
    WEBIP=$(kubectl get pod -n istioinaction -l app=webapp -o jsonpath='{.items[*].status.podIP}')
    echo $WEBIP
    
    ## veth 이름 확인
    docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }'
    WEBVETH=$(docker exec -it myk8s-control-plane ip -c route | grep $WEBIP | awk '{ print $3 }')
    echo $WEBVETH
    vetha33715c3 <- 해당 이름을 메모해두기
    --------------------------------------------------------------------------------
    
    # ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 8080 
    ## => 아래 tcp 3000에서 미러링 응답이 있지만 8080에 없다는건, webapp istio-proxy 가 최초 외부 요청자에게는 전달하지 않고 무시(drop?).
    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 8080'"
    ## 요청 
    T 2025/04/19 08:16:05.698957 10.10.0.8:39476 -> 10.10.0.19:8080 [AP] #10
    HEAD /api/catalog HTTP/1.1.
    host: webapp.istioinaction.io:30000.
    user-agent: curl/8.7.1.
    accept: */*.
    x-forwarded-for: 172.18.0.1.
    x-forwarded-proto: http.
    x-envoy-internal: true.
    x-request-id: a6cee7e5-277a-420d-a41f-78f7ca4266a4.
    x-envoy-decorator-operation: webapp.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKGwoMSU5TVEFOQ0VfSVBTEgsaCTEwLjEwLjAuOAoZCg1JU1RJT19WRVJTSU9OEggaBjEuMTcuOAqcAwoGTEFCRUxTEpEDKo4DCh0KA2FwcBIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQoTCgVjaGFydBIKGghnYXRld2F5cwoUCghoZXJpdGFnZRIIGgZUaWxsZXIKNgopaW5zdGFsbC5vcGVyYXRvci5pc3Rpby5pby9vd25pbmctcmVzb3VyY2USCRoHdW5rbm93bgoZCgVpc3RpbxIQGg5pbmdyZXNzZ2F0ZXdheQoZCgxpc3Rpby5pby9yZXYSCRoHZGVmYXVsdAowChtvcGVyYXRvci5pc3Rpby5pby9jb21wb25lbnQSERoPSW5ncmVzc0dhdGV3YXlzChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKGgoHTUVTSF9JRBIPGg1jbHVzdGVyLmxvY2FsCi4KBE5BTUUSJhokaXN0aW8taW5ncmVzc2dhdGV3YXktOTk2YmM2YmI2LThzY3BwChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ==.
    x-envoy-peer-metadata-id: router~10.10.0.8~istio-ingressgateway-996bc6bb6-8scpp.istio-system~istio-system.svc.cluster.local.
    x-envoy-attempt-count: 1.
    x-b3-traceid: d07b1cb671a13906948c4e8cdb04fc5f.
    x-b3-spanid: 948c4e8cdb04fc5f.
    x-b3-sampled: 0.
    .
    
    ## 응답
    T 2025/04/19 08:16:05.708158 10.10.0.19:8080 -> 10.10.0.8:39476 [AP] #11
    HTTP/1.1 200 OK.
    content-length: 357.
    content-type: application/json; charset=utf-8.
    date: Sat, 19 Apr 2025 08:16:05 GMT.
    x-envoy-upstream-service-time: 8.
    x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
    x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
    server: istio-envoy.
    .
    
    
    # ngrep 확인(메모 정보 직접 기입!) : webapp 파드 tcp 3000
    docker exec -it myk8s-control-plane sh -c "ngrep -tW byline -d vetha33715c3 '' 'tcp port 3000'"
    ## webapp 파드가 catalog v1 요청
    T 2025/04/19 08:19:17.965769 10.10.0.19:59682 -> 10.10.0.26:3000 [AP] #7
    GET /items HTTP/1.1.
    host: catalog.istioinaction:80.
    user-agent: beegoServer.
    x-envoy-attempt-count: 1.
    x-forwarded-for: 172.18.0.1.
    x-forwarded-proto: http.
    x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
    accept-encoding: gzip.
    x-envoy-internal: true.
    x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
    x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
    x-b3-traceid: acea9966a116ec369261bf1007049ccf.
    x-b3-spanid: a9db407fee14ea00.
    x-b3-parentspanid: 88ea1c8edba97e58.
    x-b3-sampled: 0.
    .
    
    ## webapp 파드가 catalog v2 미러링 전달
    T 2025/04/19 08:19:17.965805 10.10.0.19:49454 -> 10.10.0.27:3000 [AP] #8
    GET /items HTTP/1.1.
    host: catalog.istioinaction-shadow:80.
    user-agent: beegoServer.
    x-envoy-attempt-count: 1.
    x-forwarded-for: 172.18.0.1,10.10.0.19.
    x-forwarded-proto: http.
    x-request-id: c5918c18-4d42-4a15-9a47-3e0722239fe4.
    accept-encoding: gzip.
    x-envoy-internal: true.
    x-envoy-decorator-operation: catalog.istioinaction.svc.cluster.local:80/*.
    x-envoy-peer-metadata: ChoKDkFQUF9DT05UQUlORVJTEggaBndlYmFwcAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoMSU5TVEFOQ0VfSVBTEgwaCjEwLjEwLjAuMTkKGQoNSVNUSU9fVkVSU0lPThIIGgYxLjE3LjgKowEKBkxBQkVMUxKYASqVAQoPCgNhcHASCBoGd2ViYXBwCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ3ZWJhcHAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAogCgROQU1FEhgaFndlYmFwcC03Njg1YmNiODQtc2t6Z2cKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUAoFT1dORVISRxpFa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvd2ViYXBwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoZCg1XT1JLTE9BRF9OQU1FEggaBndlYmFwcA==.
    x-envoy-peer-metadata-id: sidecar~10.10.0.19~webapp-7685bcb84-skzgg.istioinaction~istioinaction.svc.cluster.local.
    x-b3-traceid: acea9966a116ec369261bf1007049ccf.
    x-b3-spanid: 06a47967e15bebdf.
    x-b3-parentspanid: a9db407fee14ea00.
    x-b3-sampled: 0.
    .
    
    ## catalog v1 에서 응답 받음
    T 2025/04/19 08:19:17.968372 10.10.0.26:3000 -> 10.10.0.19:59682 [AP] #11
    HTTP/1.1 200 OK.
    x-powered-by: Express.
    vary: Origin, Accept-Encoding.
    access-control-allow-credentials: true.
    cache-control: no-cache.
    pragma: no-cache.
    expires: -1.
    content-type: application/json; charset=utf-8.
    content-length: 502.
    etag: W/"1f6-ih2h+hDQ0yLLcKIlBvwkWbyQGK4".
    date: Sat, 19 Apr 2025 08:19:17 GMT.
    x-envoy-upstream-service-time: 1.
    x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI2ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjEKDwoHdmVyc2lvbhIEGgJ2MQoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIgoETkFNRRIaGhhjYXRhbG9nLTZkNWI5YmJiNjYtdnpnNGoKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KUQoFT1dORVISSBpGa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZwoXChFQTEFURk9STV9NRVRBREFUQRICKgAKGgoNV09SS0xPQURfTkFNRRIJGgdjYXRhbG9n.
    x-envoy-peer-metadata-id: sidecar~10.10.0.26~catalog-6d5b9bbb66-vzg4j.istioinaction~istioinaction.svc.cluster.local.
    server: istio-envoy.
    .
    [
      {
        "id": 1,
        "color": "amber",
        "department": "Eyewear",
        "name": "Elinor Glasses",
        "price": "282.00"
      },
    ...
    
    ## catalog v2 에서 응답 받음
    T 2025/04/19 08:19:17.968259 10.10.0.27:3000 -> 10.10.0.19:49454 [AP] #9
    HTTP/1.1 200 OK.
    x-powered-by: Express.
    vary: Origin, Accept-Encoding.
    access-control-allow-credentials: true.
    cache-control: no-cache.
    pragma: no-cache.
    expires: -1.
    content-type: application/json; charset=utf-8.
    content-length: 698.
    etag: W/"2ba-8igEisu4O69h8jWIFgUqgmp7D5o".
    date: Sat, 19 Apr 2025 08:19:17 GMT.
    x-envoy-upstream-service-time: 1.
    x-envoy-peer-metadata: ChsKDkFQUF9DT05UQUlORVJTEgkaB2NhdGFsb2cKGgoKQ0xVU1RFUl9JRBIMGgpLdWJlcm5ldGVzChwKDElOU1RBTkNFX0lQUxIMGgoxMC4xMC4wLjI3ChkKDUlTVElPX1ZFUlNJT04SCBoGMS4xNy44CrIBCgZMQUJFTFMSpwEqpAEKEAoDYXBwEgkaB2NhdGFsb2cKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwosCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgkaB2NhdGFsb2cKKwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SBBoCdjIKDwoHdmVyc2lvbhIEGgJ2MgoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKJQoETkFNRRIdGhtjYXRhbG9nLXYyLTZkZjg4NWI1NTUtbjlueHcKHAoJTkFNRVNQQUNFEg8aDWlzdGlvaW5hY3Rpb24KVAoFT1dORVISSxpJa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvaW5hY3Rpb24vZGVwbG95bWVudHMvY2F0YWxvZy12MgoXChFQTEFURk9STV9NRVRBREFUQRICKgAKHQoNV09SS0xPQURfTkFNRRIMGgpjYXRhbG9nLXYy.
    x-envoy-peer-metadata-id: sidecar~10.10.0.27~catalog-v2-6df885b555-n9nxw.istioinaction~istioinaction.svc.cluster.local.
    server: istio-envoy.
    .
    [
      {
        "id": 1,
        "color": "amber",
        "department": "Eyewear",
        "name": "Elinor Glasses",
        "price": "282.00",
        "imageUrl": "http://lorempixel.com/640/480"
      },
    ...

     

     

     


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

    ▶ 들어가며

    더보기
    • 기본적으로, 이스티오는 트래픽이 서비스 메시 밖으로 향하는 것을 허용한다.
    • 예를 들어, 애플리케이션이 서비스 메시가 관리하지 않는 외부의 웹 사이트나 서비스와 통신하려고 시도하면 이스티오는 트래픽이 나가도록 허용한다.
    • 모든 트래픽은 먼저 서비스 메시 사이드카 프록시(이스티오 프록시)를 거치므로 트래픽 라우팅을 제어할 수 있고, 이스티오의 기본 정책을 바꿔 어떤 트래픽도 메시를 떠날 수 없게 거부할 수 있다.

     

    • 어떤 트래픽도 메시를 떠날 수 없게 막는 것은, 메시 내 서비스나 애플리케이션이 손상됐을 때 악의적인 공격자가 자신의 집으로 연락하는 것을 방지하기 위한 기본적인 심층 방어 태세다.
    • 그렇지만 외부 트래픽이 이스티오를 사용할 수 없게 하는 것만으로는 충분하지 않다.
    • 손상된 파드는 프록시를 우회할 수 있기 때문이다.
    • 그러므로 3계층 및 4계층 보호 같은 추가적인 트래픽 차단 매커니즘을 갖춘 심층 방어 접근법이 필요하다.
    • 예를 들어 취약점 때문에 공격자가 특정 서비스를 제어할 수 있다면, 공격자는 자신이 제어하는 서버에 도달할 수 있도록 코드 주입이나 서비스 조작을 시도할 수 있다.
    • 공격자가 자신이 제어하는 서버에 도달할 수 있고 손상된 서비스를 더 제어할 수 있다면, 회사의 민감 데이터와 지적 재산권을 탈취할 수 있다.

    외부 트래픽을 차단하도록 이스티오를 설정해 메시에 간단한 보호 계층을 더해보자. (그림 5.11참조). (실습)

    더보기
    기본적으로 어떤 트래픽도 서비스 메시를 떠날 수 없도록 하자
    # 현재 istiooperators meshConfig 설정 확인
    kubectl get istiooperators -n istio-system -o json
    ...
                    "meshConfig": {
                        "defaultConfig": {
                            "proxyMetadata": {}
                        },
                        "enablePrometheusMerge": true
                    },
    ...
    
    # webapp 파드에서 외부 다운로드
    kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
    
    # webapp 로그 : 신규 터미널
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    [2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -
    
    # 다음 명령을 실행해 이스티오의 기본값을 ALLOW_ANY 에서 REGISTRY_ONLY 로 바꾸자.
    # 이느 서비스 메시 저장소에 명시적으로 허용된 경우(화이트 리스트)에만 트래픽이 메시를 떠나도록 허용하겠다는 의미다.
    # 아래 설정 방법 이외에도 IstioOperator 로 설정을 변경하거나, istio-system 의 istio configmap 을 변경해도 됨.
    # outboundTrafficPolicy 3가지 모드 : ALLOW_ANY (default) , REGISTRY_ONLY , ALLOW_LIST
    docker exec -it myk8s-control-plane bash
    ----------------------------------------
    istioctl install --set profile=default --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
    y 
    
    exit
    ----------------------------------------
    
    # 배포 확인
    docker exec -it myk8s-control-plane istioctl proxy-status
    NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
    catalog-6d5b9bbb66-vzg4j.istioinaction                 Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
    catalog-v2-6df885b555-n9nxw.istioinaction              Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
    istio-ingressgateway-6bb8fb6549-s4pt8.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
    webapp-7685bcb84-skzgg.istioinaction                   Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-6w77x     1.17.8
    
    
    # webapp 파드에서 외부 다운로드
    kubectl exec -it deploy/webapp -n istioinaction -c webapp -- wget https://raw.githubusercontent.com/gasida/KANS/refs/heads/main/msa/sock-shop-demo.yaml
    Connecting to raw.githubusercontent.com (185.199.109.133:443)
    wget: error getting response: Connection reset by peer
    command terminated with exit code 1
    
    # webapp 로그 : BlackHoleCluster 차단
    # UH : NoHealthyUpstream - No healthy upstream hosts in upstream cluster in addition to 503 response code.
    # https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    [2025-04-19T09:55:34.851Z] "- - -" 0 UH - - "-" 0 0 0 - "-" "-" "-" "-" "-" BlackHoleCluster - 185.199.109.133:443 10.10.0.19:34868 - -
    
    # proxy-config : webapp
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn BlackHoleCluster -o json
    [
        {
            "name": "BlackHoleCluster",
            "type": "STATIC",
            "connectTimeout": "10s"
        }
    ]
    
    # 현재 istiooperators meshConfig 설정 확인
    kubectl get istiooperators -n istio-system -o json
    ...
                    "meshConfig": {
                        "accessLogFile": "/dev/stdout",
                        "defaultConfig": {
                            "proxyMetadata": {}
                        },
                        "enablePrometheusMerge": true,
                        "extensionProviders": [
                            {
                                "envoyOtelAls": {
                                    "port": 4317,
                                    "service": "opentelemetry-collector.istio-system.svc.cluster.local"
                                },
                                "name": "otel"
                            },
                            {
                                "name": "skywalking",
                                "skywalking": {
                                    "port": 11800,
                                    "service": "tracing.istio-system.svc.cluster.local"
                                }
                            }
                        ],
                        "outboundTrafficPolicy": {
                            "mode": "REGISTRY_ONLY"
                        }
                    },

     

     

    ServiceEntry 소개 : 이스티오의 서비스 디스커버리 기능을 사용해 클러스터 외부의 서비스로 라우팅하기

    더보기
    • 모든 서비스가 서비스 메시 내에 있는 것은 아니므로, 메시 내부의 서비스가 메시 외부의 서비스와 통신할 방법이 필요하다.
    • 메시 외부의 서비스는 기존 HTTP 서비스일 수도 있고, 데이터베이스나 캐시 같은 인프라 서비스일 수도 있다.
    • 이스티오 외부에 위치한 서비스에 대해서도 정교한 라우팅을 구현할 수 있는데, 먼저 ServiceEntry 라는 개념부터 소개해야 한다.

     

    • 이스티오는 내부 서비스 저장소 internal service registry 를 구축하는데, 여기에는 메시에서 인지하고 접근할 수 있는 모든 서비스가 들어 있다.
    • 이 저장소가 메시 내 서비스가 다른 서비스를 찾는 데 사용 할 수 있는 서비스 디스커버리 저장소의 공식적인 표현이라고 생각해도 좋다.
      • You can think of this registry as the canonical representation of a service-discovery registry that services within the mesh can use to find other services.
    • 이스티오는 컨트롤 플레인을 배포한 플랫폼을 보고 내부 저장소를 구축한다.
      • Istio builds this internal registry by making assumptions about the platform on which the control plane is deployed.
    • 예를 들어 이 책에서는 컨트롤 플레인을 쿠버네티스에 배포하고 있다.
    • 이스티오는 기본 쿠버네티스 API를 사용해 자신의 서비스 카탈로그를 구축한다. 이는 그림 5.12에서 보여지는 것과 같다.
    • 메시 내부의 서비스가 메시 외부의 서비스와 통신하려면, 이스티오의 서비스 디스커버리 저장소가 이 외부 서비스를 인지하게 해야 한다.
      • Istio uses the default Kubernetes API to build its catalog of services (based on Kubernetes Service objects)
    저장소에 ServiceEntry 리소스로 외부 서비스를 추가 가능ALT ⇒ istiod는 k8s api (Service 리소스)를 통해서 서비스/엔드포인트 정보를 동적으로 발견/획득
    • 우리의 가상 상점에서는 최고의 고객 서비스를 제공하고, 고객이 직접 피드백을 하거나 서로 생각을 나눌 수 있도록 하고 싶다.
    • 이를 위해 서비스 메시 클러스터 외부에 구축하고 배포한 온라인 포럼에 사용자를 연결할 것이다.
    • 여기서 포럼은 jsonplaceholder.typicode.com URL에 있다. Free fake and reliable API for testing and prototyping.
      • API 개발 및 테스트를 쉽게 할 수 있도록 가짜 데이터(Fake Data) REST API 형태로 제공해주는 무료 서비스.
      • 각 엔드포인트는 GET, POST, PUT, DELETE 같은 메서드도 흉내낼 수 있지만 실제 데이터는 저장되지는 않음. (가상의 응답만 리턴)
      • 다음과 같은 리소스 엔드포인트를 제공

     

    • 이스티오 ServiceEntry 리소스는 이스티오의 서비스 저장소에 항목을 삽입하는 데 사용 할 수 있는 저장소 메타데이터를 캡슐화한다.
    # cat ch5/forum-serviceentry.yaml                                        
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: jsonplaceholder
    spec:
      hosts:
      - jsonplaceholder.typicode.com
      ports:
      - number: 80
        name: http
        protocol: HTTP
      resolution: DNS
      location: MESH_EXTERNAL
    • 이 ServiceEntry 리소스는 항목을 이스티오 서비스 저장소에 삽입하는데, 이는 메시 내 클라이언트가 호스트 jsonplaceholder.typicode.com 를 사용해 JSON 플레이스홀더를 호출할 수 있음을 명시한다.
    • JSON 플레이스홀더 서비스는 클러스터 외부에 있는 서비스와의 통신을 실험하는 데 사용할 수 있도록 예제 REST API를 노출한다.
    • 이 ServiceEntry를 만들기 전에 jsonplaceholder.typicode.com REST API를 호출하는 서비스를 설치하고, 이스티오가 실제로 밖으로 나가는 모든 트래픽을 막는지 관찰해보자.

     

     

    ServiceEntry (실습)

    더보기

    Step1. jsonplaceholder.typicode.com 을 사용하는 예시 포럼 애플리케이션을 설치하자.

    # forum 설치
    cat services/forum/kubernetes/forum-all.yaml
    kubectl apply -f services/forum/kubernetes/forum-all.yaml -n istioinaction
    
    # 확인
    kubectl get deploy,svc -n istioinaction -l app=webapp
    docker exec -it myk8s-control-plane istioctl proxy-status
    
    
    # webapp 웹 접속
    open http://webapp.istioinaction.io:30000/

     

    Step2. 웹 브라우저에서 http://webapp.istioinaction.io:30000/ 접속 → 새로 고침  

     

    Step3. 메시 안에서 새로운 포럼 서비스를 호출해보자

    # 메시 안에서 새로운 포럼 서비스를 호출
    curl -s http://webapp.istioinaction.io:30000/api/users
    error calling Forum service
    
    # forum 로그 확인
    kubectl logs -n istioinaction -l app=forum -c istio-proxy -f
    [2025-04-19T10:35:23.526Z] "GET /users HTTP/1.1" 502 - direct_response - "-" 0 0 0 - "172.18.0.1" "Go-http-client/1.1" "04bef923-b182-94e9-a58d-e2d9f957693b" "jsonplaceholder.typicode.com" "-" - - 104.21.48.1:80 172.18.0.1:0 - block_all
    # 클러스터 내부 서비스에서 외부 도메인(jsonplaceholder.typicode.com) 으로 나가려 했지만, Istio가 요청을 막아서 502 오류와 함께 직접 응답 처리한 상황
    ## direct_response : Envoy가 요청을 외부로 보내지 않고 자체적으로 차단 응답을 반환했음을 의미
    ## block_all : Istio에서 egress(외부) 요청이 전면 차단됨을 나타내는 메시지
    [2025-04-19T10:35:23.526Z] "GET /api/users HTTP/1.1" 500 - via_upstream - "-" 0 28 0 0 "172.18.0.1" "beegoServer" "04bef923-b182-94e9-a58d-e2d9f957693b" "forum.istioinaction:80" "10.10.0.31:8080" inbound|8080|| 127.0.0.6:60487 10.10.0.31:8080 172.18.0.1:0 - default

     

    • 이 호출을 허용하기 위해 jsonplaceholder.typicode.com 호스트에 이스티오 ServiceEntry 리소스를 만들 수 있다.
    • ServiceEntry 리소스를 만들면 이스티오의 서비스 저장소에 항목이 삽입되고, 서비스 메시가 이를 알 수 있다.
    # istio proxy (forum)
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-1.json
    
    #
    cat ch5/forum-serviceentry.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: jsonplaceholder
    spec:
      hosts:
      - jsonplaceholder.typicode.com
      ports:
      - number: 80
        name: http
        protocol: HTTP
      resolution: DNS
      location: MESH_EXTERNAL
      
    kubectl apply -f ch5/forum-serviceentry.yaml -n istioinaction
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config all deploy/forum.istioinaction -o short > forum-2.json
    diff forum-1.json forum-2.json
    25a26
    > jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS       
    96a98
    > 80                                                            jsonplaceholder.typicode.com                       /*  
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/forum.istioinaction | grep json
    80                                                            jsonplaceholder.typicode.com                       /* 
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction --fqdn jsonplaceholder.typicode.com -o json
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/forum.istioinaction | grep json
    jsonplaceholder.typicode.com                            80        -              outbound      STRICT_DNS  
    
    # 목적 hosts 의 도메인 질의 응답 IP로 확인된 엔드포인트들 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/forum.istioinaction --cluster 'outbound|80||jsonplaceholder.typicode.com'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    104.21.112.1:80     HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.16.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.32.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.48.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.64.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.80.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    104.21.96.1:80      HEALTHY     OK                outbound|80||jsonplaceholder.typicode.com
    
    
    # 메시 안에서 새로운 포럼 서비스를 호출 : 사용자 목록 반환 OK
    curl -s http://webapp.istioinaction.io:30000/api/users
    
    
    # 반복 접속
    while true; do curl -s http://webapp.istioinaction.io:30000/ ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
    
    
    # webapp 웹 접속
    open http://webapp.istioinaction.io:30000/

     

     

     

     

     


    (추가 실습) 외부에 web 서버(https://httpbin.org/ or docker container nginx 1대 기동 후)에 Istio 에 ServiceEntry 등록 후 통신 허용 설정 해보자

    더보기
    # example
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: httpbin-svc
    spec:
      hosts:
      - httpbin.org
      location: MESH_EXTERNAL
      ports:
      - number: 80
      name: httpbin
        protocol: http
      resolution: DNS

     

    Summary

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

    ▶ 5장 실습 자원 정리 

    kind delete cluster --name myk8s

    Chap6. 복원력 : 애플리케이션 네트워킹 문제 해결하기

    ☞ 이번 chapter 에서 다룰 내용 요약

    https://netpple.github.io/docs/istio-in-action/Istio-ch6-resilience

    더보기
    • 회복탄력성의 중요성 이해 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장에서 다룬 이스티오 인그레스 게이트웨이를 거쳐 클러스터 내부로 트래픽이 들어오게 되면, 트래픽을 요청 수준에서 조작할 수 있고 요청을 정확히 어디로 라우팅할지 제어할 수 있다.
    • 앞 장에서는 가중치 라우팅, 요청 비교 기반 라우팅과 이를 통해 가능해진 릴리스 패턴 유형을 다륐다.
    • 또한 애플리케이션 오류, 네트워크 파티션, 기타 주요 문제가 발생한 경우에 문제를 우회하는 데도 이 트래픽 제어를 사용할 수 있다.
    • 분산 시스템의 문제는 시스템이 이따금 예측할 수 없는 방식으로 실패하며 수작업으로 트래픽 전환 조치를 할 수 없다는 것이다.
    • 따라서 문제가 발생했을 때 애플리케이션이 스스로 대응할 수 있도록 애플리케이션에 합리적인 동작을 구축할 수 있는 방법이 필요하다.
    • 이스티오를 사용하면 애플리케이션 코드를 변경하지 않고도 타임아웃, 재시도, 서킷 브레이커를 추가할 수 있다.
    • 이번 장에서는 그 방법과 나머지 시스템에 미치는 영향을 살펴본다.

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

    더보기
    #
    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 - <<EOF
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
    - role: control-plane
      extraPortMappings:
      - containerPort: 30000 # Sample Application (istio-ingrssgateway) HTTP
        hostPort: 30000
      - containerPort: 30001 # Prometheus
        hostPort: 30001
      - containerPort: 30002 # Grafana
        hostPort: 30002
      - containerPort: 30003 # Kiali
        hostPort: 30003
      - containerPort: 30004 # Tracing
        hostPort: 30004
      - containerPort: 30005 # Sample Application (istio-ingrssgateway) HTTPS
        hostPort: 30005
      - containerPort: 30006 # TCP Route
        hostPort: 30006
      - containerPort: 30007 # kube-ops-view
        hostPort: 30007
      extraMounts: # 해당 부분 생략 가능
      - hostPath: /Users/gasida/Downloads/istio-in-action/book-source-code-master # 각자 자신의 pwd 경로로 설정
        containerPath: /istiobook
    networking:
      podSubnet: 10.10.0.0/16
      serviceSubnet: 10.200.1.0/24
    EOF
    
    # 설치 확인
    docker ps
    
    # 노드에 기본 툴 설치
    docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget bridge-utils net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'
    
    # (옵션) kube-ops-view
    helm repo add geek-cookbook 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

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

    더보기
    # 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 <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: netshoot
    spec:
      containers:
      - name: netshoot
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
    EOF

     


    6.1 Istio 의 로드밸런싱 - 지역 인식전략

    들어가며 : 복원력 패턴 필요

    더보기
    • 마이크로서비스는 복원력을 최우선으로 고려해 구축해야 한다.
    • ‘장애가 발생하지 않도록 구축하면 된다’고 말하는 세상은 현실이 아니다. 장애가 발생하면 모든 서비스를 중단시킬 위험이 있다.
    • 네트워크로 통신하는 서비스로 분산 시스템을 구축할 때는 장애 지점을 더 많이 많들어낼 위험이 있으며, 치명적인 장애가 발생할 가능성을 마주하게 된다.
    • 따라서 서비스 소유자는 애플리케이션 및 서비스 전반에 걸쳐 몇 가지 복원력 패턴을 일관되게 채택해야 한다.

     

    • 그림 6.1 처럼 서비스 A가 서비스 B를 호출했는데 서비스 B의 특정 엔드포인트로 전송이된 요청에서 지연이 발생했다면, 우리는 서비스 A가 이를 사전에 식별하고 다른 엔드포인트, 다른 가용 영역, 심지어 다른 리전으로 라우팅하길 원한다.
    • 만약 서비스 B에 간간히 오류가 발생한다면 실패한 요청을 재시도할 수 있다.
    • 마찬가지로 서비스 B를 호출하는 데 문제가 생긴다면, 그것이 무슨 문제든 서비스 B가 회복할 때까지 물러나야 할 수 있다.
    • 계속 서비스 B로 부하를 가한다면(경우에 따라 요청을 재시도하면서 부하가 증폭되는 경우도 있음) 서비스를 과부하 상태로 만들 위험이 있다.
    • 이런 과부하는 서비스 A와 이러한 서비스에 의존하는 모든 서비스에 파급돼 심각한 연쇄 오류를 일으킬 수 있다.
    • 해결책은 애플리케이션이 장애를 예상해 요청을 처리할 때 자동으로 복원을 시도하거나 대체 경로로 돌아갈 수 있도록 구축하는 것이다.
    • 예를 들어 서비스 A가 서비스 B를 호출할 때 문제가 발생한다면, 요청을 재시도하거나 타임아웃시키거나 서킷 브레이킹 패턴을 사용해 더 이상의 발신 요청을 취소할 수 있다.
    • 이번 장에서는 애플리케이션의 프로그래밍 언어와 상관없이 애플리케이션에 복원력을 올바르고 일관되게 구현할 수 있도록, 이스티오를 사용해 이런 문제들을 투명하게 해결하는 방법을 살펴본다.

    6.1.1 Building resilience into application libraries : 언어 마다 구현 상이, 운영 부담

    더보기

     

    • 이런 프레임워크의 한 가지 문제점은 언어, 프레임워크, 인프라 조합마다 구현 방식이 상이하다는 것이다.
    • 트위터 FinagleNetfilxOSS는 자바 개발자에게는 훌륭하지만 Node.js, Go 언어, 파이썬 개발자는 이런 패턴의 변형을 찾거나 직접 구현해야 했다.
    • 때에 따라서는 이런 라이브러리가 애플리케이션 코드에 침입해 네트워킹 코드가 여거저기 흩어지고 실제 비즈니스 로직을 가려버리는 상황이 발생하기도 했다.
    • 나아가 여러 언어와 프레임워크에 걸쳐 이런 라이브러리들을 유지 관리하는 것은 마이크로서비스 운영 측면에서 부담이 된다.
    • 모든 조합을 동시에 패치하고 기능을 동일하게 유지해야 하기 때문이다.

     

    6.1.2 Using Istio to solve these problems : 이스티오 서비스 프록시가 복원력 기능 제공

    더보기
    • 앞 장에서 봤듯이, 이스티오의 서비스 프록시는 애플리케이션 옆에 위치하며 애플리케이션을 드나드는 모든 네트워크 트래픽을 처리한다. (그림 6.2)
    • 이스티오에서 서비스 프록시는 애플리케이션 수준 요청과 메시지(HTTP 요청 등)를 이해하므로 프록시 안에서 복원력 기능을 구현할 수 있다.
    • 예를 들어 서비스 호출 시 HTTP 503 오류가 발생하면 세 번까지 재시도하도록 설정할 수 있다.
    • 재시도할 실패, 재시도 횟수, 재시도별 타임아웃을 정확히 설정할 수 있는데, 서비스 프록시가 서비스 인스턴스마다 배포되므로 애플리케이션마다 필요한 대로 재시도 동작을 세밀하게 제어할 수 있다.
    • 이스티오의 다른 복원력 설정도 모두 마찬가지다.
    • 이스티오의 서비스 프록시는 기본적으로 다음과 같은 복원력 패턴을 구현한다.
      • 클라이언트 측 로드 밸런싱 Client-side load balancing
      • 지역 인식 로드 밸런싱 Locality-aware load balancing
      • 타임아웃 및 재시도 Timeouts and retries
      • 서킷 브레이킹 Circuit breaking

     

     

    6.1.3 Decentralized implementation of resilience : 복원력 패턴을 분산 구현

    더보기
    • 이스티오를 사용하면, 애플리케이션 요청이 통과하는 데이터 플레인 프록시가 애플리케이션 인스턴스와 같은 위치에 있고 중앙집중식 게이트웨이가 필요하지 않음을 알 수 있다.
    • 이런 복원력 패턴 처리를 코드에 함께 두는 애플리케이션 라이브러리를 사용하면 동일한 아키텍처를 얻는다.
    • 예전에는 이런 분산 시스템의 공통 문제 중 일부를 해결하기 위해 비싸고 변경하기 어려운 중앙집중식 하드웨어 장치와 기타 소프트웨어 미들웨어를 요청 경로에 배치헀다. (하드웨어 로드 밸런싱, 메시징 시스템, 엔터프라이즈 서비스 버스, API 관리 등).
    • 이런 초기 구현들은 더 정적인 환경에 맞춰 만들어져서, 고도로 동적이고 탄력적인 클라우드 아키텍처와 인프라에 맞게 확장되거나 잘 대응하지 못한다.
    • 따라서 이런 일부 복원력 패턴을 해결할 때는 분산 구현을 선택해야 한다.
    • 다음 절에서는 이스티오가 도움이 될 수 있는 복원력 패턴을 살펴볼 것이다.
    • 서비스 작동 방식을 좀 더 세밀하게 제어하기 위해 다른 샘플 애플리케이션 셋을 사용하는데, 이 프로젝트는 좀 더 현실적인 운영 환경에서 서비스가 어떻게 작동할 수 있는지 설명하기 위해 만든 닉 잭슨 Nic Jackson의 Fake Service 라는 프로젝트다.
    • https://github.com/nicholasjackson/fake-service
    • 다음 예제에서는 그림 6.3과 같이 simple-web 서비스가 simple-backend 백엔드를 호출하는 것을 볼 수 있다.

     

     

     

     

    [ Istio 로드 밸런싱 전략 비교 ]  - ref. by ChatGPT  

    더보기

     


    전략 명 설명 장점 단점 사용 사례
    Round Robin 요청을 순서대로 분산 단순, 예측 가능 성능 차이 무시 기본 설정, 균등 분산 필요할 때
    Least Request 요청 수가 가장 적은 인스턴스 선택 부하 분산 최적화 응답 시간 고려 안 함 트래픽 급증 상황, 비동기 서비스
    Random 무작위 인스턴스 선택 구현 간단, 빠름 트래픽 분산 불균형 가능 테스트 환경, 작은 서비스
    Weighted 가중치에 따라 비율 분산 Canary, A/B에 적합 가중치 관리 필요 Canary 릴리스, 트래픽 분할 테스트
    Maglev (해시) 클라이언트별 요청을 동일 인스턴스로 고정 세션 고정 가능, 캐시 효율 증가 쏠림 현상 가능 로그인 세션 유지, 상태 기반 서비스
    Locality-Aware 가능한 가까운 리전/존의 인스턴스에 우선 분산 지연 감소, 고가용성, 네트워크 비용 절감 설정 복잡도 있음 멀티리전/멀티AZ 운영 환경, 글로벌 서비스

     

    [ Istio 내 설정 예시 ]

    ☞ DestinationRule 안에서 LoadBalancer 항목으로 설정할 수 있다.

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: my-service
    spec:
      host: my-service
      trafficPolicy:
        loadBalancer:
          simple: LEAST_REQUEST

     

    지역 인식 전략의 장점과 단점


    항목 설명
    장점 지연 최소화, 네트워크 비용 절감, 장애 시 탄력적 대응
    단점 리전 간 트래픽 설정이 복잡할 수 있음
    사용 사례 멀티 리전 서비스, 글로벌 사용자 대상 시스템, 고가용성 API

     

    ♣ localityLbSetting 예제 – 지역 인식 + 장애 대비 구조

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: my-service
    spec:
      host: my-service.default.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        localityLbSetting:
          enabled: true            # 지역 인식 로드 밸런싱 활성화
          failover:
          - from: ap-northeast-2   # 서울 리전
            to: ap-northeast-1     # 도쿄 리전
          - from: ap-northeast-1
            to: us-west-1          # 미국 서부

     

    ☞ 코드 추가 설명


    항목 설명
    enabled: true 지역 인식 로드 밸런싱을 활성화
    failover 해당 지역(from)에 서비스 인스턴스가 없거나 오류일 경우, to 지역으로 트래픽 전환
    simple: ROUND_ROBIN 같은 지역 내 인스턴스 간에는 Round Robin으로 부하 분산

     


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

    ▶ 들어가며 : EDS, DestinationRule(Client LoadBalancer)

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

    ☞ Server side load balancing vs. Client side load balancing 용어 정리와 비교(장/단점)

     

    • 서버 사이드 로드 밸런싱 (Server-Side Load Balancing)
      클라이언트의 요청은 먼저 중앙의 로드 밸런서로 모인다. 로드 밸런서는 상태, 부하, 헬스체크 정보를 기반으로 최적의 서버로 트래픽을 분배한다. 네트워크 경계에서 일관된 정책 적용이 가능하다.
    • 클라이언트 사이드 로드 밸런싱 (Client-Side Load Balancing)
      클라이언트가 직접 여러 서버의 엔드포인트 목록을 알고, 자신이 판단한 부하·헬스 상태·알고리즘에 따라 요청을 골라 전송한다. 서비스 메시(microservices) 환경에서 흔히 사용되며, 제어 권한이 분산되어 있다.

     


    구분 항목 서버 사이드 로드 밸런싱 클라이언트 사이드 로드 밸런싱
    위치 네트워크 경계(외부) 혹은 프록시 계층 각 클라이언트 애플리케이션 내부
    제어 권한 중앙 로드 밸런서 클라이언트
    트래픽 분배 시점 요청 수신 직후 요청 시점에 직접 결정
    헬스체크 방식 로드 밸런서가 주기적 헬스체크 수행 클라이언트가 서비스 디스커버리 또는 헬스 API 활용
    확장성 로드 밸런서 용량 한계, 수평 확장 필요 클라이언트만 늘리면 됨, 제어 지점 다수 분산
    지연 시간 영향 추가 홉 발생(로드 밸런서) 직접 서버로 요청, 홉 수 감소
    구성 복잡도 중앙 설정 필요, 네트워크 구성 복잡 서비스 디스커버리·라이브러리 통합 필요
    장애 대응 로드 밸런서 장애 시 싱글 포인트 위험 클라이언트 분산돼 단일 장애점 감소
    대표 기술/도구 AWS ELB, NGINX, HAProxy Netflix Ribbon, Istio Envoy, gRPC client balancer
    적합한 사용 사례 전통적인 웹 서비스, 엔터프라이즈 애플리케이션 마이크로서비스, 서비스 메시, 그레이디언트 배포

     

     

    6.2.1 Getting started with client-side load balancing : DestinationRule 실습

    Step0. 실습 전 초기화

    더보기
    kubectl delete gw,vs,deploy,svc,destinationrule --all -n istioinaction

    Step1. 예제 서비스 2개와 gw,vs 배포

    더보기
    # (옵션) kiali 에서 simple-backend-1,2 버전 확인을 위해서 labels 설정 : ch6/simple-backend.yaml
    open ch6/simple-backend.yaml
    ...
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-backend
        version: v1
      name: simple-backend-1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: simple-backend
      template:
        metadata:
          labels:
            app: simple-backend
            version: v1
    ...
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-backend
        version: v2
      name: simple-backend-2
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: simple-backend
      template:
        metadata:
          labels:
            app: simple-backend
            version: v2
    ...
    
    # 예제 서비스 2개 배포
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction
    kubectl apply -f ch6/simple-web.yaml -n istioinaction
    
    # 확인
    kubectl get deploy,pod,svc,ep -n istioinaction -o wide
    NAME                               READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS       IMAGES                                 SELECTOR
    deployment.apps/simple-backend-1   1/1     1            1           105m   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
    deployment.apps/simple-backend-2   2/2     2            2           105m   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
    deployment.apps/simple-web         1/1     1            1           105m   simple-web       nicholasjackson/fake-service:v0.17.0   app=simple-web
    ...
    
    # gw,vs 배포
    cat ch6/simple-web-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: simple-web-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "simple-web.istioinaction.io"
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-web-vs-for-gateway
    spec:
      hosts:
      - "simple-web.istioinaction.io"
      gateways:
      - simple-web-gateway
      http:
      - route:
        - destination:
            host: simple-web
            
    kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction
    
    # 확인
    kubectl get gw,vs -n istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-status
    NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
    istio-ingressgateway-996bc6bb6-ztcx5.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
    simple-backend-1-7449cc5945-d9zmc.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
    simple-backend-2-6876494bbf-vdttr.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
    simple-backend-2-6876494bbf-zn6v9.istioinaction       Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
    simple-web-7cd856754-tjdv6.istioinaction              Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-xmjbj     1.17.8
    
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       simple-web.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 3
    
    # 호출
    curl -s http://simple-web.istioinaction.io:30000
    open http://simple-web.istioinaction.io:30000
    
    # 신규 터미널 : 반복 접속 실행 해두기
    while true; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    
    
    # 로그 확인
    kubectl stern -l app=simple-web -n istioinaction
    kubectl stern -l app=simple-web -n istioinaction -c istio-proxy
    kubectl stern -l app=simple-web -n istioinaction -c simple-web
    kubectl stern -l app=simple-backend -n istioinaction
    kubectl stern -l app=simple-backend -n istioinaction -c istio-proxy
    kubectl stern -l app=simple-backend -n istioinaction -c simple-backend
    
    
    # (옵션) proxy-config
    # proxy-config : simple-web
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/simple-web.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction | grep backend
    80                                                            simple-backend, simple-backend.istioinaction + 1 more...     /* 
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local
    SERVICE FQDN                                       PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
    simple-backend.istioinaction.svc.cluster.local     80       -          outbound      EDS
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
    ...
           "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80||simple-backend.istioinaction.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST",
    ...
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.14:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.15:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.16:8080     HEALTHY     OK                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
    ...

     

    simple-backend 트래픽 분산 비중이 1:2 정도로 출력됨. 이유는?

     

    ☞ V1에 POD 1개, V2 에 POD 2개 존재함!!


    [ 실행 결과 - 한 눈에 보기 ]

    1) 서비스 배포

     2) gw배포 및 확인

     3) 서비스 구성 상세확인

      

    Step2. Istio Destination Rule - Round Robin 설정

    더보기
    • 이스티오 DestinationRule 리소스로 simple-backend 서비스를 호출하는 모든 클라이언트의 로드 밸런싱을 ROUND_ROBIN으로 설정하자.
    • **DestinationRule**는 특정 목적지를 호출하는 메시 내 클라이언트들에 정책을 지정한다.
    • simple-backend 용 첫 DestinationRule는 다음과 같다. 
    # cat ch6/simple-backend-dr-rr.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN # 엔드포인트 결정을 '순서대로 돌아가며'

     

    • 적용 및 확인해보자
      • simple-web 은 simple-backend 를 호출하는데, simple-backend 서비스에는 복제본이 여러게 있다.
      • 이는 의도한 것으로, 런타임에 일부 엔드포인트를 수정해볼 것이다.
    # DestinationRule 적용 : ROUND_ROBIN
    cat ch6/simple-backend-dr-rr.yaml
    kubectl apply -f ch6/simple-backend-dr-rr.yaml -n istioinaction
    
    # 확인 : DestinationRule 단축어 dr
    kubectl get dr -n istioinaction
    NAME                HOST                                             AGE
    simple-backend-dr   simple-backend.istioinaction.svc.cluster.local   11s
    
    kubectl get destinationrule simple-backend-dr -n istioinaction \
     -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'
    ROUND_ROBIN
    
    # 호출 : 이 예시 서비스 집합에서는 호출 체인을 보여주는 JSON 응답을 받느다
    ## simple-web 서비스는 simple-backend 서비스를 호출하고, 우리는 궁극적으로 simple-backend-1 에서 온 응답 메시지 Hello를 보게 된다.
    ## 몇 번 더 반복하면 simple-backend-1 과 simple-backend-2 에게 응답을 받는다. 
    curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"
    
    # 반복 호출 확인 : 파드 비중은 backend-2가 2개임
    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
    
    
    # 로그 확인 : backend 요청을 하면 요청을 처리할 redirect 주소를 응답 (301), 전달 받은 redirect(endpoint)로 다시 요청
    kubectl stern -l app=simple-web -n istioinaction -c istio-proxy
    ## simpleweb → simple-backend (301) redirect 응답 수신
    simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.317Z] "GET // HTTP/1.1" 301 - via_upstream - "-" 0 36 3 3 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-backend:80" "10.10.0.16:8080" outbound|80||simple-backend.istioinaction.svc.cluster.local 10.10.0.17:46590 10.200.1.161:80 172.18.0.1:0 - default
    ## simpleweb → simple-backend (200)
    simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.324Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 278 156 156 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-backend:80" "10.10.0.14:8080" outbound|80||simple-backend.istioinaction.svc.cluster.local 10.10.0.17:38336 10.200.1.161:80 172.18.0.1:0 - default
    ## simpleweb → 외부 curl 응답(200)
    simple-web-7cd856754-tjdv6 istio-proxy [2025-04-20T04:22:24.307Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 889 177 177 "172.18.0.1" "curl/8.7.1" "ee707715-7e7c-42c3-a404-d3ee22f79d11" "simple-web.istioinaction.io:30000" "10.10.0.17:8080" inbound|8080|| 127.0.0.6:40981 10.10.0.17:8080 172.18.0.1:0 outbound_.80_._.simple-web.istioinaction.svc.cluster.local default
    
    kubectl stern -l app=simple-backend -n istioinaction -c istio-proxy
    ## simple-backend → (응답) simpleweb (301)
    simple-backend-2-6876494bbf-zn6v9 istio-proxy [2025-04-20T04:22:45.209Z] "GET // HTTP/1.1" 301 - via_upstream - "-" 0 36 3 3 "172.18.0.1" "curl/8.7.1" "71ba286a-a45f-41bc-9b57-69710ea576d7" "simple-backend:80" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:54105 10.10.0.14:8080 172.18.0.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default
    ## simple-backend → (응답) simpleweb (200)
    simple-backend-1-7449cc5945-d9zmc istio-proxy [2025-04-20T04:22:45.216Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 278 152 152 "172.18.0.1" "curl/8.7.1" "71ba286a-a45f-41bc-9b57-69710ea576d7" "simple-backend:80" "10.10.0.15:8080" inbound|8080|| 127.0.0.6:43705 10.10.0.15:8080 172.18.0.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
    ...
           "name": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80||simple-backend.istioinaction.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST", # RR은 기본값(?)이여서, 해당 부분 설정이 이전과 다르게 없다
    ...
    
    # 
    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

     

    • simple-web 과 simple-backend 간 호출이 여러 simple-backend 엔드포인트로 효과적으로 분산되는 것을 확인할 수 있다.
    • 우리는 simple-web 과 simple-backend 간의 클라이언트 측 로드 밸런싱을 보고 있는데, simple-web 과 함께 배포된 서비스 프록시가 모든 simple-backend 엔드포인트를 알고 있고 기본 알고리듬을 사용해 요청을 받을 엔드포인트를 결정하고 있기 때문이다.
    • ROUND_ROBIN 로드 밸런싱을 사용하도록 DestinationRule 을 설정하기는 했지만, 사실 이스티오 서비스 프록시의 기본 설정도 ROUND_ROBIN 로드 밸런싱 전략을 사용하는 것이다.
    • 클라이언트 측 로드 밸런싱은 어떻게 서비스의 복원력을 기여할 수 있을까?
    • 부하 생성기를 사용해 simple-backend 서비스 지연 시간을 변화시키는 어느 정도 현실적인 시나리오를 살펴보자.
    • 그러면 이런 상황에서 어떤 이스티오의 로드 밸런싱 전략이 가장 적합한지 선택하는 데 도움이 될 것이다.

    [ 실행 결과 - 한 눈에 보기 ]

     

    1) DR - RR로 설정

     

     2) Traffic 확인 ( V1 : V2 = 1: 2  <== POD 갯수 반영 )

    6.2.2 Setting up our scenario : Fortio 설치

    더보기
    • 현실적인 환경에서 서비스가 요청을 처리하는 데 시간이 걸린다. 소요 시간은 여러 이유로 달라질 수 있다.
      • 요청 크기 Request size
      • 처리 복잡도 Processing complexity
      • 데이터베이스 사용량 Database usage
      • 시간이 걸리는 다른 서비스 호출 Calling other services that take time
    • 서비스 외적인 이유도 응답 시간에 영향을 줄 수 있다.
      • 예기치 못한, 모든 작업을 멈추는 stop-the-world 가비지 컬렉션 Unexpected, stop-the-world garbage collections
      • 리소스 경합 Resource contention (CPU, 네트워크 등)
      • 네트워크 혼잡 Network congestion
    • 예제 서비스에서는 이를 모방하고자 응답 시간에 지연 delays 과 편차(변인) variance 를 도입해본다.
    • 서비스를 다시 호출하고 최초 설정한 서비스 응답 시간의 차이를 관찰하자.
    # 호출 3회 반복 : netshoot 에서 서비스명으로 내부 접속
    kubectl exec -it netshoot -- time curl -s -o /dev/null http://simple-web.istioinaction
    kubectl exec -it netshoot -- time curl -s -o /dev/null http://simple-web.istioinaction
    kubectl exec -it netshoot -- time curl -s -o /dev/null http://simple-web.istioinaction
    real    0m 0.17s
    user    0m 0.00s
    sys     0m 0.00s
    ...

     

    • 서비스를 호출 할 때마다 응답 시간이 달라진다.
    • 로드 밸런싱은 주기적으로 혹은 예기치 못하게 지연 시간이 급증하는 엔드포인트의 영향을 줄이는 효과적인 전략이 될 수 있다.

    • 우리는 Fortio 라는 CLI 부하 생성 도구를 사용해 서비스를 실행하고 클라이언트 측 로드 밸런싱의 차이를 관찰할 것이다.
      • Fortio load testing library, command line tool, advanced echo server and web UI in go (golang).
      • Allows to specify a set query-per-second load and record latency histograms and other useful stats.
    • https://github.com/fortio/fortio , 프리뷰 https://demo.fortio.org/
    # mac 설치
    brew install fortio
    fortio -h
    fortio server
    open http://127.0.0.1:8080/fortio
    
    # windows 설치
    1. 다운로드 https://github.com/fortio/fortio/releases/download/v1.69.3/fortio_win_1.69.3.zip
    2. 압축 풀기
    3. Windows Command Prompt : fortio.exe server
    4. Once fortio server is running, you can visit its web UI at http://localhost:8080/fortio/
    '포르티오' 는 그리스어로 '부하' 라는 뜻이다!!

     

    • Fortio 가 우리 서비스를 호출할 수 있는지 확인
    fortio curl http://simple-web.istioinaction.io:30000
    14:16:29.874 r1 [INF] scli.go:122> Starting, command="Φορτίο", version="1.69.3 h1:G1cy4S0/+JKwd1fuAX+1jKdWto4fPpxAdJHtHrWzF1w= go1.24.2 arm64 darwin", go-max-procs=8
    HTTP/1.1 200 OK
    ...

     

    [ 실행 결과 - 한 눈에 보기 ]

    1) fortio 실행

    6.2.3 Testing various client-side load-balancing strategies* : LB 알고리즘에 따란 지연 시간 성능 측정

    더보기
    그림 출처 https://netpple.github.io/docs/istio-in-action/Istio-ch6-resilience
    • 이제 Fortio 로드 테스트 클라이언트를 사용할 준비가 됐으므로 사용 사례를 살펴보자.
    • Fortio를 사용해서 60초 동안 10개의 커넥션을 통해 초당 1000개의 요청을 보낼 것이다.
      • Fortio to send 1,000 rps (requests per seconds) for 60 seconds through 10 connections
    • Fortio는 각 호출의 지연 시간을 추적하고 지연 시간 백분위수 분석과 함께 히스토그램에 표시한다.
    • 테스트를 하기 전에 지연 시간을 1초까지 늘린 simple-backend-1 서비스를 도입할 것이다.
    • 이는 엔드포인트 중 하나에 긴 가비지 컬렉션 이벤트 또는 기타 애플리케이션 지연 시간이 발생한 상황을 시뮬레이션한다.
    • 우리는 로드 밸런싱 전략을 라운드 로빈, 랜덤, 최소 커넥션으로 바꿔가면서 차이점을 관찰할 것이다.

    ☞ 지연된 simple-backend-1 서비스를 배포해보자 : fake-service - Dockerhub , Github  

    #
    cat ch6/simple-backend-delayed.yaml
    ...
          - env:
            - name: "LISTEN_ADDR"
              value: "0.0.0.0:8080"
            - name: "SERVER_TYPE"
              value: "http"                      
            - name: "NAME"
              value: "simple-backend"      
            - name: "MESSAGE"
              value: "Hello from simple-backend-1"                     
            - name: "TIMING_VARIANCE"
              value: "10ms"                              
            - name: "TIMING_50_PERCENTILE"
              value: "1000ms"                                      
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            image: nicholasjackson/fake-service:v0.17.0
    ...
    kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
    kubectl rollout restart deployment -n istioinaction simple-backend-1
    
    # 확인???
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
    TIMING_VARIANCE=10ms
    TIMING_50_PERCENTILE=150ms # ???
    
    kubectl exec -it deploy/simple-backend-2 -n istioinaction -- env | grep TIMING
    TIMING_VARIANCE=10ms
    TIMING_50_PERCENTILE=150ms
    
    # 직접 deploy 편집 수정???
    KUBE_EDITOR="nano" kubectl edit deploy/simple-backend-1 -n istioinaction
    ...
          - name: TIMING_50_PERCENTILE
              value: 1000ms
    ...
    
    kubectl rollout restart deployment -n istioinaction simple-backend-1
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
    TIMING_VARIANCE=10ms
    TIMING_50_PERCENTILE=150ms # ???
    
    
    # 동작 중 파드에 env 직접 수정..
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    -----------------------------------
    export TIMING_50_PERCENTILE=1000ms
    exit
    -----------------------------------
    
    #
    kubectl describe pod -n istioinaction -l app=simple-backend | grep TIMING_50_PERCENTILE:
          TIMING_50_PERCENTILE:  1000ms
          TIMING_50_PERCENTILE:  150ms
          TIMING_50_PERCENTILE:  150ms
    
    # 테스트
    curl -s http://simple-web.istioinaction.io:30000 | grep duration  
    curl -s http://simple-web.istioinaction.io:30000 | grep duration 
    curl -s http://simple-web.istioinaction.io:30000 | grep duration              
      "duration": "1.058699s",
          "duration": "1.000934s",
    Jeger에서 1초 지연 인

     

     

    ☞ Fortio 를 서버 모드로 실행 후 웹 대시보드에 접근하자. fortio server

    • 웹 대시보드에서는 테스트에 인자를 입력하고, 테스트를 실행하고, 결과를 시각화 할 수 있다. http://127.0.0.1:8080/fortio/

     

    • 테스트가 완료되면 결과 파일이 파일시스템이 저장된다. 또한 결과 그래프도 표시된다.
    • 라운드 로빈 밸런싱 전략의 경우 지연 시간 결과 ⇒ 75분위수에서 응답이 1초 이상 걸림.
    • target 50% 0.189584
    • target 75% 1.03067
    • target 90% 1.10307
    • target 99% 1.14652
    • target 99.9% 1.15086

    ☞ 로드 밸런싱 알고리듬을 RANDOM 으로 변경하고 다시 로드 테스트를 해보자.

    #
    cat ch6/simple-backend-dr-random.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: RANDOM
    
    kubectl apply -f ch6/simple-backend-dr-random.yaml -n istioinaction
    
    # 확인
    kubectl get destinationrule simple-backend-dr -n istioinaction \
     -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep lbPolicy
    "lbPolicy": "RANDOM",

     

    • ortio 동일하게 테스트 수행 ⇒ Start 클릭
    • RANDOM 로드 밸런싱 알고리듬 지연 시간 결과 ⇒ ⇒ 75분위수에서 응답이 1초 이상 으로 RoundRobin 과 비슷함
    # target 50% 0.19306
    # target 75% 1.02324
    # target 90% 1.05923
    # target 99% 1.08082
    # target 99.9% 1.08298

     


    ☞ 로드 밸런싱 알고리듬을 Least connection 으로 변경하고 다시 로드 테스트를 해보자.

    #
    cat ch6/simple-backend-dr-least-conn.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: LEAST_CONN
    
    kubectl apply -f ch6/simple-backend-dr-least-conn.yaml -n istioinaction
    
    # 확인
    kubectl get destinationrule simple-backend-dr -n istioinaction \
     -o jsonpath='{.spec.trafficPolicy.loadBalancer.simple}{"\n"}'
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep lbPolicy
    "lbPolicy": "LEAST_REQUEST",
    • fortio 동일하게 테스트 수행 ⇒ Start 클릭
    • Least connection로드 밸런싱 알고리듬 지연 시간 결과 ⇒ ⇒ 75분위수에서 응답이 200ms(0.19..) 이내 응답성능으로, RR, Random 보다 좋음!
    # target 50% 0.183548
    # target 75% 0.195571
    # target 90% 1.00836
    # target 99% 1.02942
    # target 99.9% 1.03153

     

    ☞ 실습 완료 후 Ctrl+C로 Fortio 서버를 종료하자.


    [ 실행 결과 -한 눈에 보기 ]

     

    1) delay Service 설정

     

    2) R-R 부하, fortio 분석

     

    3) Random 부하 설정, fortio 분석

     4) Least Connection 부하 , fortio 분석

     

     

    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 지역 인식 로드 밸런싱 (실습)

     들어가며

    더보기
    • 이스티오 같은 컨트롤 플레인의 역할 중 하나는 서비스 토폴로지를 이해하고 그 토폴로지가 어떻게 발전할 수 있는지 파악하는 것이다.
    • 서비스 메시에서 전체 서비스 토폴로지를 이해할 때의 이점은 서비스와 피어 서비스의 위치 같은 휴리스틱 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 디플로이먼트는 다음과 같을 수 있다.
    # cat ch6/simple-service-locality.yaml
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-web
      name: simple-web
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: simple-web
      template:
        metadata:
          labels:
            app: simple-web
            istio-locality: us-west1.us-west1-a
    ...
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-backend
      name: simple-backend-1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: simple-backend
      template:
        metadata:
          labels:
            app: simple-backend
            istio-locality: us-west1.us-west1-a
            version: v1 # 추가해두자!
    ...
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: simple-backend
      name: simple-backend-2
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: simple-backend
      template:
        metadata:
          labels:
            app: simple-backend
            istio-locality: us-west1.us-west1-b
            version: v2 # 추가해두자!
    ...

     

    • simple-backend 서비스를 배포할 때 지역 레이블을 다양하게 지정할 것이다.
    • simple-web과 같은 지역인 us-west1-a 에 simple-backend-1을 배포한다.
    • 그리고 us-west1-b 에 simple-backend-2 를 배포한다. 이 경우, 리전은 동일하지만 영역이 다르다.
    • 지역 간에 로드 밸런싱을 수행할 수 있는 이스티오의 기능에는 리전, 영역, 심지어는 더 세밀한 하위 영역 subzone 도 포함된다.

    ☞ 서비스 배포

    #
    kubectl apply -f ch6/simple-service-locality.yaml -n istioinaction
    
    # 확인
    ## simple-backend-1 : us-west1-a  (same locality as simple-web)
    kubectl get deployment.apps/simple-backend-1 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
    us-west1.us-west1-a
    
    ## simple-backend-2 : us-west1-b
    kubectl get deployment.apps/simple-backend-2 -n istioinaction \
    -o jsonpath='{.spec.template.metadata.labels.istio-locality}{"\n"}'
    us-west1.us-west1-b

     

    • 이스티오의 지역 인식 로드 밸런싱은 기본적으로 활성화돼 있다. https://istio.io/v1.17/docs/reference/config/istio.mesh.v1alpha1/
      • Locality aware load balancing is enabled by default
    • 비활성화 하고 싶다면, meshConfig.localityLbSetting.enabled: false 로 설정하면 된다.
    • 책 저자가 추천하는 이스티오의 지역 인식 로드 밸런싱 블로깅 https://karlstoney.com/locality-aware-routing/
      • 앞서 시뮬레이션한 것치럼, 클러스터의 노드가 여러 가용성 영역에 배포돼 있다면 기본 지역 인식 로드 밸런싱이 항상 바람직하지는 않을 수도 있음을 고려해야 한다.
      • 우리 예제에서는 어느 지역에서도 simple-backend(목표 서비스) 복제본이 simple-web(호출하는 서비스)보다 적지 않다.
      • 그러나 실제 환경에서는 특정 지역에서 호출하는 서비스 인스턴스 보다 목표 서비스 인스턴스가 적게 배포될 수도 있다.
      • 이런 경우 목표 서비스가 과부하를 겪을 수 있고, 시스템 전체의 부하가 의도한 것처럼 불균형하게 분산될 수 있다.
      • 결국 부하 특성과 토폴로지에 맞춰 로드 밸런싱을 튜닝하는 것이 중요하다.

    [ 테스트 예상 결과 ]

    - 지역 정보가 준비되면, 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 모든 엔드포인트로 트래픽이 로드 밸런싱 됨

    # 신규 터미널 : 반복 접속 실행 해두기
    while true; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    
    # 호출 : 이 예시 서비스 집합에서는 호출 체인을 보여주는 JSON 응답을 받느다
    curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"
    
    # 반복 호출 확인 : 파드 비중은 backend-2가 2개임
    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
    • 이스티오에서 지역 인식 로드밸런싱이 작동하려면 헬스 체크를 설정해야 한다.
    • 헬스 체크가 없으면 이스티오가 로드 밸런싱 풀의 어느 엔드포인트가 비정상 인지, 다음 지역으로 넘기는 판단 기준은 무엇인지를 알 수 없다.
      • Istio does not know which endpoints in the load-balancing pool are unhealthy and what heuristics to use to spill over into the next locality.
    • 이상값 감지는 엔드포인트의 동작과, 엔드포인트가 정상적으로 보이는지 여부를 수동적으로 감시한다.
    • 엔드포인트가 오류를 반환하는지 지켜보다가, 오류가 반환되면 엔드포인트를 비정상으로 표시한다.
    • 이상값 감지는 다음 절에서 더 자세히 다룬다.

    ☞ simple-backend 서비스에 이상값 감지를 설정해 수동적인 헬스 체크 설정을 추가해보자.  

    #
    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
          interval: 5s
          baseEjectionTime: 30s
          maxEjectionPercent: 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
    ...

     

    - 모든 서비스 Trafficsimple-backend-1 서비스로 가고 있다.


    ☞ 호출 테스트 2 ⇒ 오동작 주입 후 확인

    • 트래픽이 가용 영역을 넘어가는 것을 보기 위해 simple-backend-1 서비스를 오동작 상태로 만들어보자.
    • simple-web 에서 simple-backend-1 호출하면 항상 HTTP 500 오류를 발생하게 하자
    # HTTP 500 에러를 일정비율로 발생
    cat ch6/simple-service-locality-failure.yaml
    ...
            - name: "ERROR_TYPE"
              value: "http_error"           
            - name: "ERROR_RATE"
              value: "1"                              
            - name: "ERROR_CODE"
              value: "500"  
    ...
    kubectl apply -f ch6/simple-service-locality-failure.yaml -n istioinaction
    
    # simple-backend-1- Pod 가 Running 상태로 완전히 배포된 후에 호출 확인
    
    # 반복 호출 확인 : 파드 비중 확인
    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
    
    
    # 확인
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'        
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.23:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.24:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.25:8080     HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local
    
    # simple-backend-1 500에러 리턴으로 outliercheck 실패 상태로 호출에서 제외됨
    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": {
                        "failedOutlierCheck": true,
                        "edsHealthStatus": "HEALTHY"
                    },
                    ...
                    "healthStatus": {
                        "edsHealthStatus": "HEALTHY"
                    },

     

    • 이렇게 특정 지역의 서비스가 제대로 동작하지 않을 때 예상하는 지역 인식 로드 밸런싱 결과를 얻을 수 있다.
    • 이 지역 인식 로드 밸런싱은 단일 클러스터 내부에서 이뤄지는 것임을 유의하자

    ▶  다음 실습을 위해 simple-backend-1 을 정상화

    kubectl apply -f ch6/simple-service-locality.yaml -n istioinaction

    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로 보낼 것이다.

    Step1. lb에 가중치 적용하기!

    #
    cat ch6/simple-backend-dr-outlier-locality.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        loadBalancer: # 로드 밸런서 설정 추가
          localityLbSetting:
            distribute:
            - from: us-west1/us-west1-a/* # 출발지 영역
              to:
                "us-west1/us-west1-a/*": 70 # 목적지 영역
                "us-west1/us-west1-b/*": 30 # 목적지 영역
        connectionPool:      
          http:
            http2MaxRequests: 10
            maxRequestsPerConnection: 10
        outlierDetection:
          consecutive5xxErrors: 1
          interval: 5s
          baseEjectionTime: 30s
          maxEjectionPercent: 100
    
    kubectl apply -f ch6/simple-backend-dr-outlier-locality.yaml -n istioinaction
    
    
    # 반복 호출 확인 : 파드 비중 확인
    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
    
    
    # endpoint 에 weight 는 모두 1이다. 위 70/30 비중은 어느곳의 envoy 에 설정 되는 걸까?...
    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
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.23:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.24:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.26:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

     

    • 일부 요청이 분산됐다. 대부분은 가장 가까운 지역으로 향했지만, 다음 최인접 지역으로 넘어갈 수 있는 여지는 있었다.
    • 이는 5장에서 트래픽을 명시적으로 제어했던 것과 동일하지 않다는 점에 유의하자.
    • 트래픽 라우팅에서는 서비스의 부분집합 간에 트래픽 비중을 제어할 수 있었고, 보통 전체 서비스 그룹 내에서 종류나 버전이 여럿일 때 사용한다.
    • 위 예제에서는 서비스의 배포 토폴로지를 바탕으로 트래픽에 가중치를 부여했고, 부분집합과는 무관한다.
    • 부분집합 라우팅과 가중치 부여는 상호 배타적인 개념이 아니다.
    • 이 둘은 중첩될 수 있으며, 5장에서 봤던 세밀한 트래픽 제어 및 라우팅을 이번 절에서 살펴본 지역 인식 로드 밸런서 위에 적용 할 수 있다.

    [ 실행 결과 - 한 눈에 보기 ]

     

     

     


    6.4 Transparent timeouts and retries (실습)

    들어가며 : 네트워크 신뢰성 문제 극복 필요

    더보기
    • 네트워크에 분산된 구성 요소에 의존하는 시스템을 구축할 때 가장 큰 문제는 지연 시간실패다.
    • 앞선 절에서는 이스티오에서 로드 밸런싱과 지역을 사용해 이런 문제를 완화하는 방법을 살펴봤다.
    • 이 네트워크 호출이 너무 길면 어떻게 되는가?
    • 또는 지연 시간이나 다른 네트워크 요인 때문에 간간이 실패한다면?
    • 이스티오는 이런 문제를 해결하는 데 어떻게 도움이 될 수 있을까?
    • 이스티오를 사용하면 다양한 종류의 타임아웃과 재시도를 설정해 네트워크에 내재된 신뢰성 문제를 극복할 수 있다.

     

    6.4.1 Timeouts : 지연 시간

    더보기
    • 분산 환경에서 가장 다루기 어려운 시나리오 중 하나가 지연 시간이다.
    • 처리 속도가 느려지면 리소스를 오래 들고 있을 테고, 서비스에서는 처리해야 할 작업이 적체될 수 있으며, 상황은 연쇄 장애로까지 이어질 수 있다.
    • 이런 예기치 못한 시나리오를 방지하려면 커넥션이나 요청, 혹은 둘 다에서 타임아웃을 구현해야 한다.

     

    • 중요한 점서비스 호출 사이의 타임아웃이 서로 상호작용하는 방법이다.
    • 예를 들어 서비스 A가 서비스 B를 호출할 때 타임아웃은 1초이지만 서비스 B가 서비스 C를 호출할 때 타임이웃은 2초라면, 어떤 타임아웃이 먼저 작동하는가?
    • 가장 제한적인 타임아웃이 먼저 동작하므로, 서비스 B에서 서비스 C로의 타임아웃은 발동하지 않을 것이다.
    • 일반적으로 아키텍처의 가장자리(트래픽이 들어오는 곳)에 가까울수록 타임아웃이 길고 호출 그래프의 계층이 깊을수록 타임아웃이 짧은(혹은 더 제한적인)것이 합리적이다.
      • 통상, 밖 → 안, backend에 위치할 수록 timeout 을 짧게 설정합니다
    • 어떻게 이스티오를 사용해 타임아웃 정책을 제어하는지 살펴보자.

    Step1. 환경 재설정

    kubectl apply -f ch6/simple-web.yaml -n istioinaction
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction
    kubectl delete destinationrule simple-backend-dr -n istioinaction

     

    Step2. 호출 테스트 : simple-backend-1 1초 delay로 응답  

    # 호출 테스트 : 보통 10~20ms 이내 걸림
    curl -s http://simple-web.istioinaction.io:30000 | jq .code
    time curl -s http://simple-web.istioinaction.io:30000 | jq .code
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    
    # simple-backend-1를 1초 delay로 응답하도록 배포
    cat ch6/simple-backend-delayed.yaml
    kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
    
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep TIMING
    TIMING_VARIANCE=10ms
    TIMING_50_PERCENTILE=150ms
    
    # 동작 중 파드에 env 직접 수정..
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    -----------------------------------
    export TIMING_50_PERCENTILE=1000ms
    exit
    -----------------------------------
    
    # 호출 테스트 : simple-backend-1로 로드밸런싱 될 경우 1초 이상 소요 확인
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...
    curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 6% cpu 0.200 total
    jq .code  0.00s user 0.00s system 3% cpu 0.199 total
    500
    ...

     

    • 1초 정도는 괜찮을 수 있지만, simple-backend의 지연 시간이 5초, 혹은 100초로 급증한다면 어떻게 될까?
    • 이스티오를 사용해 simple-backend 서비스 호출에 타임아웃을 적용해보자.
    • 이스티오는 VirtualService 리소스로 요청별로 타임아웃을 지정할 수 있다.
    • 예를 들어 메시 내 클라이언트에서 simple-backend 로 향하는 호출의 타임아웃을 0.5초로 지정하려면 다음과 같이 하면 된다.
    # 
    cat ch6/simple-backend-vs-timeout.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        timeout: 0.5s
        
    kubectl apply -f ch6/simple-backend-vs-timeout.yaml -n istioinaction
    
    #
    kubectl get vs -n istioinaction
    NAME                        GATEWAYS                 HOSTS                             AGE
    simple-backend-vs                                    ["simple-backend"]                14s
    simple-web-vs-for-gateway   ["simple-web-gateway"]   ["simple-web.istioinaction.io"]   6h11m
    
    
    # 호출 테스트 : 0.5s 이상 걸리는 호출은 타임아웃 발생 (500응답)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...
    curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 2% cpu 0.537 total
    jq .code  0.00s user 0.00s system 0% cpu 0.535 total
    500
    ...
    
    # istio-proxy config 에서 위 timeout 적용 envoy 설정 부분 찾아 두자.

     

    - 다음 절에서 타임아웃 같은 실패를 해결하기 위한 다른 방법을 논의한다.

     

     

    6.4.2 Retries* : 재시도

    더보기
    • 설정 초기화
    kubectl apply -f ch6/simple-web.yaml -n istioinaction
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction

     

    • 서비스를 호출할 때 간간이 네트워크 실패를 겪는다면, 애플리케이션이 요청을 재시도하길 원할 수 있다.
    • 요청을 재시도하지 않으면, 서비스가 흔히 발새하고 예견할 수 있는 실패에 취약해져 사용자에게 좋지 않은 경험을 제공할 수 있다.
    • 한편으로 무분별한 재시도는 연쇄 장애를 야기하는 등 시스템 상태를 저하시킬 수 있으므로 적절히 균형을 맞춰야 한다.
    • 서비스가 실제로 과부화된 경우에는 재시도해봐야 문제가 악화시킬 뿐이다.
    • 이스티오의 재시도 옵션을 살펴보자.
    • 이스티오에서는 재시도가 기본적으로 활성화돼 있고, 두 번까지 재시도한다.
    • 동작을 세밀하게 튜닝하기에 앞서 기본 동작을 이해해야 한다.
    • 시작하기 위해 예제 애플리케이션에서 기본적인 재시도를 비활성화하자.
    • VirtualService 리소스에서 최대 재시도를 0으로 설정하면 된다.
    #
    docker exec -it myk8s-control-plane bash
    ----------------------------------------
    # Retry 옵션 끄기 : 최대 재시도 0 설정
    istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
    y
    exit
    ----------------------------------------
    
    # 확인
    kubectl get istiooperators -n istio-system -o yaml
    ...
        meshConfig:
          defaultConfig:
            proxyMetadata: {}
          defaultHttpRetryPolicy:
            attempts: 0
          enablePrometheusMerge: true
    ...
    
    # istio-proxy 에서 적용 부분 찾아보자

    • 에러 발생 시 재시도 실습  
    • 이제 주기적으로(75%) 실패하는 simple-backend 서비스 버전을 배포해보자.
    • 이 경우 엔드포인트 셋 중 하나(simple-backend-1)는 그림 6.12처럼 호출 중 75%에 HTTP 503 반환한다.
    #
    cat ch6/simple-backend-periodic-failure-503.yaml
    ...
            - name: "ERROR_TYPE"
              value: "http_error"           
            - name: "ERROR_RATE"
              value: "0.75"                              
            - name: "ERROR_CODE"
              value: "503"  
    ...
    
    #
    kubectl apply -f ch6/simple-backend-periodic-failure-503.yaml -n istioinaction
    
    #
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep ERROR
    
    #
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    ---------------------------------------------------------------
    export ERROR_TYPE=http_error
    export ERROR_RATE=0.75
    export ERROR_CODE=503
    exit
    ---------------------------------------------------------------
    
    # 호출테스트 : simple-backend-1 호출 시 failures (500) 발생
    # simple-backend-1 --(503)--> simple-web --(500)--> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...
    curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 6% cpu 0.200 total
    jq .code  0.00s user 0.00s system 3% cpu 0.199 total
    500
    ...
    
    # app, istio-proxy log 에서 500, 503 로그 확인해보자.
    503 응답 확인
    500 응답 확
    • 기본적으로, 이스티오는 호출이 실패하면 두 번 더 시도한다. 이 기본 재시도는 특정 상황에서만 적용된다.
    • 일반적으로는 이들 기본 상황에서는 재시도해도 안전한다.
    • 이 상황들은 네트워크 커넥션이 수립되지 않아 첫 시도에서 요청이 전송될 수 없음을 의미하기 때문이다.
      • 커넥션 수립 실패 connect-failure
      • 스트림 거부됨 refused-stream
      • 사용 불가 gRPC 상태 코드 14
      • 취소됨 gRPC 상태 코드 1
      • 재시도할 수 있는 상태 코드들 이스티오에서 기본값은 HTTP 503

    • 우리는 앞서 살펴본 설정으로 기본 재시도 정책을 비활성화했다.
    • 다음 VirtualService 리소스를 사용해 simple-backend 로 향하는 호출에 재시도를 2회로 명시적으로 설정해보자.
    #
    cat ch6/simple-backend-enable-retry.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        retries:
          attempts: 2
    
    kubectl apply -f ch6/simple-backend-enable-retry.yaml -n istioinaction
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/simple-web.istioinaction --name 80 -o json
    ...
                   "name": "simple-backend.istioinaction.svc.cluster.local:80",
                    "domains": [
                        "simple-backend.istioinaction.svc.cluster.local",
                        "simple-backend",
                        "simple-backend.istioinaction.svc",
                        "simple-backend.istioinaction",
                        "10.200.1.161"
                    ],
                    "routes": [
                        {
                            "match": {
                                "prefix": "/"
                            },
                            "route": {
                                "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                    "numRetries": 2,
    ...
    
    # 호출테스트 : 모두 성공!
    # simple-backend-1 --(503, retry 후 정상 응답)--> simple-web --> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    
    # app, istio-proxy log 에서 503 로그 확인해보자.
    simple-backend-1 실패 시, 다른 엔드포인트로 자동 재시도 후 정상 응답을 받고 나면, 최종적으로 클라이언트에게 정상 응답을 리턴해줌

     

     


    • 앞에서 봤듯이, 실패는 있지만 호출자에게는 드러나지 않는다. 이스티오의 재시도 정책을 활성화해 이런 오류를 우회하게끔 했기 때문이다.
    • HTTP 503은 기본적으로 재시도할 수 있는 상태 코드 중 하나다.
    • 다음 VirtualService 재시도 정책은 설정할 수 있는 재시도 파라미터를 보여준다.
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        retries:
          attempts: 2 # 최대 재시도 횟수
          retryOn: gateway-error,connect-failure,retriable-4xx # 다시 시도해야 할 오류
          perTryTimeout: 300ms # 타임 아웃
          retryRemoteLocalities: true # 재시도 시 다른 지역의 엔드포인트에 시도할지 여부
    • 다양한 재시도 설정으로 재시도 동작(얼마나 많이, 얼마나 깊게, 어느 엔드포인트로 재시도할 지)과 어떤 상태 코드일 때 재시도할지를 어느 정도 제어할 수 있다. 상술했듯이 모든 요청을 재시도할 수 있거나 해야 하는 것은 아니다.
    • 예를 들어 HTTP 500 코드를 반환하는 simple-backend 서비스를 배포하면, 기본 재시도 동작은 실패를 잡아내지 않는다.

    ☞ 503 이외의 다른 에러 발생 시에도 retry 가 동작하는지 확인해보자.  

    # 500 에러 코드 리턴
    cat ch6/simple-backend-periodic-failure-500.yaml
    ...
            - name: "ERROR_TYPE"
              value: "http_error"           
            - name: "ERROR_RATE"
              value: "0.75"                              
            - name: "ERROR_CODE"
              value: "500"
    ...
    
    kubectl apply -f ch6/simple-backend-periodic-failure-500.yaml -n istioinaction
    
    #
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    ---------------------------------------------------------------
    export ERROR_TYPE=http_error
    export ERROR_RATE=0.75
    export ERROR_CODE=500
    exit
    ---------------------------------------------------------------
    
    # envoy 설정 확인 : 재시도 동작(retryOn) 에 retriableStatusCodes 는 503만 있음.
    docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
    ...
                           "route": {
                                "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                    "numRetries": 2,
                                    "retryHostPredicate": [
                                        {
                                            "name": "envoy.retry_host_predicates.previous_hosts",
                                            "typedConfig": {
                                                "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                            }
                                        }
                                    ],
                                    "hostSelectionRetryMaxAttempts": "5",
                                    "retriableStatusCodes": [
                                        503
                                    ]
                                },
                                "maxGrpcTimeout": "0s"
                            },
    ...
    
    
    # 호출테스트 : Retry 동작 안함.
    # simple-backend-1 --(500, retry 안함)--> simple-web --(500)> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...
    curl -s http://simple-web.istioinaction.io:30000  0.01s user 0.01s system 30% cpu 0.036 total
    jq .code  0.00s user 0.00s system 14% cpu 0.035 total
    200
    curl -s http://simple-web.istioinaction.io:30000  0.00s user 0.01s system 5% cpu 0.184 total
    jq .code  0.00s user 0.00s system 2% cpu 0.183 total
    500
    ...
    • HTTP 500은 재시도하는 상태 코드에 포함되지 않는다.
    • 모든 HTTP 500 코드(커넥션 수립 실패 및 스트림 거부 포함)를 재시도하는 VirtualService 재시도 정책을 사용해보자.
    #
    cat ch6/simple-backend-vs-retry-500.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        retries:
          attempts: 2
          retryOn: 5xx # HTTP 5xx 모두에 재시도
    
    kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction
    
    # 호출테스트 : 모두 성공!
    # simple-backend-1 --(500, retry)--> simple-web --(200)> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done

     

    [ 타임아웃에 따른 재시도 ]

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

     

    [ 작동 방식 ]

    더보기
    • 요청이 이스티오 서비스를 서비스 프록시를 거쳐 흐를 때, 업스트림으로 전달되는 데 실패하면 요청을 ‘실패 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를 사용한다.

     

    # cat ch6/simple-backend-ef-retry-status-codes.yaml                                            
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: simple-backend-retry-status-codes
      namespace: istioinaction
    spec:
      workloadSelector:
        labels:
          app: simple-web
      configPatches:
      - applyTo: HTTP_ROUTE
        match:
          context: SIDECAR_OUTBOUND
          routeConfiguration:
            vhost:
              name: "simple-backend.istioinaction.svc.cluster.local:80"          
        patch:
          operation: MERGE
          value:
            route:
              retry_policy: # 엔보이 설정에서 직접 나온다?
                retry_back_off: 
                  base_interval: 50ms # 기본 간격을 늘린다
                retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
                - 408
                - 400

     


    • EnvoyFilter API는 ‘비상용(break glass)’ 해결책이다. 일반적으로 이스티오의 API는 기저 데이트 플레인에 대한 추상화다.
    • 엔보이 API는 이스티오 릴리즈마다 바뀔 수 있으므로 반드시 운영 환경에 넣은 엔보이 필터가 유효한지 확인해야 하며, 하위 호환성을 전제하면 안된다.
    • EnvoyFilter 리소스로 엔보이의 HTTP 필터를 설정하는 자세한 내용은 14장을 참조하자.

    ☞ 여기서는 엔보이 API를 직접 사용해 재시도 정책 설정값을 설정하고 재정의한다. 적용해보자.

    # 408 에러코드를 발생
    kubectl apply -f ch6/simple-backend-periodic-failure-408.yaml -n istioinaction
    
    # 파드 정상 기동 후 수정
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    ---------------------------------------------------------------
    export ERROR_TYPE=http_error
    export ERROR_RATE=0.75
    export ERROR_CODE=408
    exit
    ---------------------------------------------------------------
    
    # 호출테스트 : 408 에러는 retryOn: 5xx 에 포함되지 않으므로 에러를 리턴.
    # simple-backend-1 --(408)--> simple-web --(500)--> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...

     

     

    ☞ 408 에러도 재시도 적용

    #
    cat ch6/simple-backend-ef-retry-status-codes.yaml
    ...
        patch:
          operation: MERGE
          value:
            route:
              retry_policy: # 엔보이 설정에서 직접 나온다?
                retry_back_off: 
                  base_interval: 50ms # 기본 간격을 늘린다
                retriable_status_codes: # 재시도할 수 있는 코드를 추가한다
                - 408
                - 400
    
    kubectl apply -f ch6/simple-backend-ef-retry-status-codes.yaml -n istioinaction
    
    # 확인
    kubectl get envoyfilter -n istioinaction -o json
    kubectl get envoyfilter -n istioinaction
    NAME                                AGE
    simple-backend-retry-status-codes   45s
    
    # VirtualService 에도 재시도 할 대상 코드 추가
    cat ch6/simple-backend-vs-retry-on.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        retries:
          attempts: 2
          retryOn: 5xx,retriable-status-codes # retryOn 항목에 retriable-status-codes 를 추가
    
    kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction
    
    # envoy 설정 확인 : 재시도 동작(retryOn) 에 5xx 과 retriableStatusCodes 는 408,400 있음.
    docker exec -it myk8s-control-plane istioctl proxy-config route deploy/simple-web.istioinaction --name 80 -o json
    ...
                            "route": {
                                "cluster": "outbound|80||simple-backend.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "5xx,retriable-status-codes",
                                    "numRetries": 2,
                                    "retryHostPredicate": [
                                        {
                                            "name": "envoy.retry_host_predicates.previous_hosts",
                                            "typedConfig": {
                                                "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
                                            }
                                        }
                                    ],
                                    "hostSelectionRetryMaxAttempts": "5",
                                    "retriableStatusCodes": [
                                        408,
                                        400
                                    ],
                                    "retryBackOff": {
                                        "baseInterval": "0.050s"
                                    }
                                },
                                "maxGrpcTimeout": "0s"
                            }
    ...
    
    # 호출테스트 : 성공
    # simple-backend-1 --(408, retry 성공)--> simple-web --> curl(외부)
    for in in {1..10}; do time curl -s http://simple-web.istioinaction.io:30000 | jq .code; done
    ...

    ☞ 요청 헤징 REQUEST HEDGING

    • 재시도에 대한 마지막 이야기는 이스티오 API에서도 직접 노출하지 않는 고급 주제를 중심으로 한다.
    • 요청이 임계값에 도달해 시간을 초과하면 요청 헤징을 수행하도록 선택적으로 엔보이를 설정할 수 있다.
    • 요청 헤징 request hedging 이란, 요청이 타임아웃되면 다른 호스트로도 요청을 보내 원래의 타임아웃된 요청과 ‘경쟁 race’ 시키는 것을 말한다.
    • 경쟁한 요청이 성공적으로 반환되면, 그 응답을 원래 다운스트림 호출자에게 보낸다.
    • 만약 경쟁한 요청보다 원본 요청이 먼저 반환되면 원본 요청이 다운스트림 호출자에게 반환된다.

    - 요청 헤징을 설정하려면 다음 EnvoyFilter 리소스를 사용한다.

    # cat ch6/simple-backend-ef-retry-hedge.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: simple-backend-retry-hedge
      namespace: istioinaction
    spec:
      workloadSelector:
        labels:
          app: simple-web
      configPatches:
      - applyTo: VIRTUAL_HOST
        match:
          context: SIDECAR_OUTBOUND
          routeConfiguration:
            vhost:
              name: "simple-backend.istioinaction.svc.cluster.local:80"          
        patch:
          operation: MERGE
          value:
            hedge_policy:
              hedge_on_per_try_timeout: true

    더보기

     

    [ Summary ]

    더보기
    • 이번 절에서 봤듯이, 타임아웃과 재시도에 대한 주제는 그리 간단하지 않다.
    • 서비스에 적절한 타임아웃 및 재시도 정책을 설정하는 것은 어려운 일이며, 둘이 어떻게 연결될 수 있을지를 고려하면 더욱 그렇다.
    • 타임아웃과 재시도를 잘못 설정하면 시스템 아키텍처에서 의도치 않은 동작을 증폭시켜 시스템을 과부하시키고 연쇄 장애를 일으킬 수도 있다.
    • 복원력 있는 아키텍처를 구축하는 과정에서 마지막 퍼즐 조작재시도를 모두 건너뛰는 것이다.
    • 즉, 재시도하는 대신 빠르게 실패한다.
    • 부하를 더 늘리는 대신에 업스트림 시스템이 복구될 수 있도록(회복 시간 벌기) 부하를 잠시 제한할 수 있으며, 이를 위해 서킷 브레이커를 사용할 수 있다.

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    6.5 Istio의 서킷 브레이킹

    더보기

    1. Istio 서킷 브레이킹이란?

     - Envoy Proxy 기반인 Istio는 "Destination Rule"을 통해 서킷 브레이킹 기능을 사용할 수 있다. 

       기본적으로 다음과 같은 이상 상황을 감지하여 요청을 차단하거나 빠르게 실패시켜 전체 시스템을 보호해 주는 것을 목적으로 한다.

     

    • 연결 수가 너무 많을 때
    • 연결당 요청 수가 너무 많을 때
    • 응답 지연이 길어지거나 타임아웃이 반복될 때

     

     2. Circuit Breaker 핵심 파라메터


    파라메터 설명
    maxConnections 한 클러스터(서비스)로 동시에 열 수 있는 최대 TCP 연결 수
    http1MaxPendingRequests HTTP1 요청 큐에 대기 가능한 최대 요청 수
    maxRequestsPerConnection 하나의 연결에서 처리할 수 있는 최대 요청 수
    consecutiveErrors 연속 실패 횟수 기준 (Outlier Detection에 사용)
    interval, baseEjectionTime, maxEjectionPercent 실패 노드 제거 기준들

    3. Circuit Breaker 예제

    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: my-service-cb
    spec:
      host: my-service.default.svc.cluster.local
      trafficPolicy:
        connectionPool:
          tcp:
            maxConnections: 1
          http:
            http1MaxPendingRequests: 1
            maxRequestsPerConnection: 1
        outlierDetection:
          consecutive5xxErrors: 3
          interval: 5s
          baseEjectionTime: 15s
          maxEjectionPercent: 50

     

    ☞ 시나리오 요약 

    • 사용자 요청이 Istio 사이드카로 들어옴
    • Envoy는 연결 풀 상태 확인
      • 열려 있는 연결 수 < maxConnections? → OK
      • 큐에 요청 추가 < http1MaxPendingRequests? → OK
      • 아니면 → 즉시 실패(Fail Fast)
    • 연속적으로 5xx 응답이 3번 발생하면 → 해당 인스턴스를 일시적으로 퇴출(Ejection)
    • 일정 시간 후 자동 복귀 시도 (회복)

     

     

     

    들어가며 : 서킷 브레개

    더보기
    • 서킷 브레이커 기능을 사용하면 부분적이거나 연쇄적인 장애를 방지할 수 있다.
    • 비정상 시스템을 계속 과부하시켜 회복을 방해하지 않도록 비정상 시스템으로 향하는 트래픽을 줄이고 싶다.
    • 예를 들어 simple-web 서비스가 simple-backend 서비스를 호출하고 simple-backend 는 연속된 호출에서 오류를 반환하면, 계속 재시도해 시스템에 스트레스를 더 주는 대신 simple-backend로의 호출을 모두 멈추고 싶을 수 있다.
    • 이 방식은 집의 전기 시스템에서 회로 차단기가 동작하는 방식과 의도가 비슷하다.
    • 시스템에 단락이 있거나 고장이 반복되면, 회로 차단기는 회로를 개방해 나머지 시스템을 보호하도록 설계된다.
    • 서킷 브레이커 패턴은 네트워크 호출이 실패할 수 있고 실제로 실패한다는 사실을 애플리케이션이 처리하게함으로써 전체 시스템을 연쇄 실패로부터 보호하는 데 도움이 된다.

    6.5.1 Guarding against slow services wih connection-pool control* : 커넥션 풀 제어로 느린 서비스에 대응하기

    더보기

    (옵션) tracing 샘플링을 기본 1% → 100% 늘려두기

    # tracing.sampling=100
    docker exec -it myk8s-control-plane bash
    ----------------------------------------
    istioctl install --set profile=default --set meshConfig.accessLogFile=/dev/stdout --set meshConfig.defaultConfig.tracing.sampling=100 --set meshConfig.defaultHttpRetryPolicy.attempts=0
    y
    exit
    ----------------------------------------
    
    # 확인
    kubectl describe cm -n istio-system istio
    ...
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:15012
      proxyMetadata: {}
      tracing:
        sampling: 100.0
        zipkin:
          address: zipkin.istio-system:9411
    ...
    
    # 적용 : rollout 
    kubectl rollout restart deploy -n istio-system istiod
    kubectl rollout restart deploy -n istio-system istio-ingressgateway
    kubectl rollout restart deploy -n istioinaction simple-web
    kubectl rollout restart deploy -n istioinaction simple-backend-1

    Step0. 실습환경 구성

    # 현재 적용되어 있는 상태
    kubectl apply -f ch6/simple-web.yaml -n istioinaction
    kubectl apply -f ch6/simple-web-gateway.yaml -n istioinaction
    kubectl apply -f ch6/simple-backend-vs-retry-on.yaml -n istioinaction
    
    # destinationrule 삭제
    kubectl delete destinationrule --all -n istioinaction
    
    # simple-backend-2 제거
    kubectl scale deploy simple-backend-2 --replicas=0 -n istioinaction
    
    # 응답지연(1초)을 발생하는 simple-backend-1 배포
    kubectl apply -f ch6/simple-backend-delayed.yaml -n istioinaction
    
    # 동작 중 파드에 env 직접 수정..
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    -----------------------------------
    export TIMING_50_PERCENTILE=1000ms
    exit
    -----------------------------------
    
    # 테스트
    curl -s http://simple-web.istioinaction.io:30000 | grep duration              
      "duration": "1.058699s",
          "duration": "1.000934s",

     

    Step1. Circuit Breaker 실습

     - 이스티오의 커넥션 제한 서킷 브레이커 테스트 시작해볼 수 있다. 아주 간단한 로드 테스트를 실행해보자.

     

    # 초당 요청을 하나 보내는(-qps1) 커넥션 하나(-c1)로 진행 : 백엔드가 대략 1초 후 반환하므로 트래픽이 원활하고 성공률이 100%여야 한다.
    fortio load -quiet -jitter -t 30s -c 1 -qps 1 http://simple-web.istioinaction.io:30000
    ...
    # target 50% 1.02364
    # target 75% 1.02788
    # target 90% 1.03042
    # target 99% 1.03195
    # target 99.9% 1.0321
    ...
    Code 200 : 30 (100.0 %)
    All done 30 calls (plus 1 warmup) 1023.868 ms avg, 1.0 qps

     

    • 커넥션 및 요청 제한을 도입하고 어떤 일이 일어나는지 살펴보자. 아주 간단한 제한으로 시작한다 - Docs
      • maxConnections : 커넥션 총 개수, 커넥션 오버플로 connection overflow 를 보고할 임계값이다.
        • 이스티오 프록시(엔보이)는 이 설정에 정의된 상한까지 서비스 요청에 커넥션을 사용한다.
        • 실제 커넥션 최댓값은 로드 밸런싱 풀의 엔드포인트 개수에 설정값을 더한 숫자다.
        • 이 값을 넘길 때마가 엔보이는 자신의 메트릭에 그 사실을 보고한다.
        • Maximum number of HTTP1 /TCP connections to a destination host.
      • http1MaxPendingRequests : 대기 중인 요청, 사용할 커넥션이 없어 보류 중인 요청을 얼마나 허용할지를 의미하는 숫자다.
        • The allowable number of requests that are pending and don’t have a connection to use.
        • Maximum number of requests that will be queued while waiting for a ready connection pool connection. Default 1024.
      • http2MaxRequests : 모든 호스트에 대한 최대 동시 요청 개수, 안타깝게도 이 설정은 이스티오에서 이름을 잘못 붙였다.
        • 내부적으로 이 숫자는 클러스터 내 모든 엔드포인트/호스트에 걸쳐 있는 병렬 요청의 최대 개수를 제어하는데, HTTP 2인지 HTTP 1.1인지는 상관없다. https://github.com/istio/istio/issues/27473
        • This setting is unfortunately misnamed in Istio. Under the covers, it controls the maximum number of parallel requests across all endpoints/hosts in a cluster regardless of HTTP2 or HTTP1.1.
        • Maximum number of active requests to a destination. Default 1024.
    # cat ch6/simple-backend-dr-conn-limit.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        connectionPool:
          tcp:
            maxConnections: 1 # 커넥션 총 개수 Total number of connections
          http:
            http1MaxPendingRequests: 1 # 대기 중인 요청 Queued requests
            maxRequestsPerConnection: 1 # 커넥션당 요청 개수 Requests per connection
            maxRetries: 1 # Maximum number of retries that can be outstanding to all hosts in a cluster at a given time.
            http2MaxRequests: 1 # 모든 호스트에 대한 최대 동시 요청 개수 Maximum concurrent requests to all hosts
    
    # DestinationRule 적용 (connection-limiting) 
    kubectl apply -f ch6/simple-backend-dr-conn-limit.yaml -n istioinaction
    kubectl get dr -n istioinaction
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction | egrep 'RULE|backend'
    SERVICE FQDN                                            PORT      SUBSET     DIRECTION     TYPE             DESTINATION RULE
                                                            8080      -          inbound       ORIGINAL_DST     simple-backend-dr.istioinaction
    simple-backend.istioinaction.svc.cluster.local          80        -          outbound      EDS              simple-backend-dr.istioinaction
    
    # 설정 적용 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
    ...
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST",
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 1, # tcp.maxConnections, 커넥션 총 개수 Total number of connections
                        "maxPendingRequests": 1, # http.http1MaxPendingRequests, 대기 중인 요청 Queued requests
                        "maxRequests": 1, # http.http2MaxRequests, 모든 호스트에 대한 최대 동시 요청 개수 
                        "maxRetries": 1, # http.maxRetries
                        "trackRemaining": true
                    }
                ]
            },
            "typedExtensionProtocolOptions": {
                "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
                    "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
                    "commonHttpProtocolOptions": { 
                        "maxRequestsPerConnection": 1 # http.maxRequestsPerConnection, 커넥션당 요청 개수
    ...
    
    # (참고) 기본값?
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn simple-web.istioinaction.svc.cluster.local -o json
    ...
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST",
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295,
                        "trackRemaining": true
    ...

     

    - 테스트를 다시 실행해 이 설정을 검증하다. 커넥션 하나에 초당 요청을 하나 보낼 때 제대로 동작해야 한다.

     

    # 초당 요청을 하나 보내는(-qps1) 커넥션 하나(-c1)로 진행 : 
    fortio load -quiet -jitter -t 30s -c 1 -qps 1 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 1 (for perfect keepalive, would be 1)
    Uniform: false, Jitter: true, Catchup allowed: false
    Code 200 : 30 (100.0 %)
    All done 30 calls (plus 1 warmup) 1023.648 ms avg, 1.0 qps

     


     Step3. 수집 통계 활성화

     

    • 정확한 확인을 위해서 이스티오 서비스 프록시에서 더 많은 통계 수집을 활성화하자. ⇒ 서킷 브레이크 영향인지 vs 업스트림의 장애인지 확인
      • 기본적으로 이스티오 서비스 프록시(엔보이)에는 각 클러스터에 대한 통계가 많지만 이스티오가 통계를 잘라낸다.
      • 이는 수집 에이전트(프로메테우스 등)가 통계의 큰 카디널리티 cardinality 에 압도되지 않게 하기 위해서다.
      • simple-web 서비스에 통계 수집을 활성화 해보자. simple-web 서비스는 simple-backend 서비스를 호출한다.
    # simple-web 디플로이먼트에 sidecar.istio.io/statsInclusionPrefixes="cluster.<이름>" 애너테이션 추가하자
    ## sidecar.istio.io/statsInclusionPrefixes: cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local
    cat ch6/simple-web-stats-incl.yaml | grep statsInclusionPrefixes 
            sidecar.istio.io/statsInclusionPrefixes: "cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local"        
    kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction
    
    # 정확한 확인을 위해 istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # simple-web 에 istio-proxy 의 stats 조회
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
    
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

    (참고) 혹시 istio-proxy 상세 로그 확인 필요 시

    # 
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?connection\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?conn_handler\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?http2\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?multi_connection\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?pool\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?router\=debug
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -c istio-proxy -- curl -X POST http://localhost:15000/logging\?upstream\=debug

     


    Step4. 부하 보내기 ( 결과 모니터링 )

     

    1) Envoy 메트릭 ****- Docs

    • upstream_cx_overflow : maxConnections 초과
      • Total times that the cluster’s connection circuit breaker overflowed
    • upstream_rq_pending_overflow : 대기 중인 요청 Queued requests, http1MaxPendingRequests 초과
      • Total requests that overflowed connection pool or requests (mainly for HTTP/2 and above) circuit breaking and were failed.
    • 커넥션 개수와 초당 요청 수를 2로 늘리면 어떨까? 2개의 커넥션에서 요청을 초당 하나씩 보내기 시작해보자.
      • 커넥션과 요청이 지정한 임계값(병렬 요청이 너무 많거나 요청이 너무 많이 쌓임)을 충분히 넘겨 서킷 브레이커를 동작시켰음을 확인.
    # 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 요청이 17개 실패한 것으로 반환됐다(HTTP 5xx)
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 19 (for perfect keepalive, would be 2)
    Code 200 : 30 (63.8 %)
    Code 500 : 17 (36.2 %)
    All done 47 calls (plus 2 warmup) 925.635 ms avg, 1.5 qps
    ...
    
    # 로그 확인 : simple-web
    kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
    ...
    # 오류 요청 (503 Service Unavailable, UO 플래그
    ## HTTP 503: 서비스가 일시적으로 사용 불가능. Envoy가 업스트림 서버(simple-backend:80)에 요청을 전달하지 못함.
    ## UO 플래그: "Upstream Overflow"로, Envoy의 서킷 브레이커가 트리거되었거나 최대 연결/요청 제한에 도달했음을 의미.
    ## upstream_reset_before_response_started{overflow}: 업스트림 서버가 응답을 시작하기 전에 연결이 리셋되었으며, 이는 오버플로우(리소스 제한)로 인함.
    [2025-04-22T03:17:24.830Z] "GET // HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 4 - ...
    
    # 오류 요청 (500 Internal Server Error) : 최종 사용자에게 500 에러 리턴
    [2025-04-22T03:17:24.825Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 687 11 11 ...
    ## simple-web 서비스에서 backend 정보를 가져오지 못하여 애플리케이션 레벨 오류 발생
    ## HTTP 500: 서버 내부 오류. 업스트림 서버(simple-web:30000)가 요청을 처리하는 중 예기치 않은 오류 발생.
    ## via_upstream: 오류가 Envoy가 아니라 업스트림 서버에서 발생했음을 나타냄.
    ...
    
    # 통계 확인 : 18개로 +/- 1개 정도는 무시하고 보자. 성능 테스트 실패 갯수(17개)와 아래 통계값이 일치 한다(18-1).
    # 큐 대기열이 늘어나 결국 서킷 브레이커를 발동함. 
    # fail-fast 동작은 이렇게 보류 중 혹은 병행 요청 갯수가 서킷 브레이커 임계값을 넘어 수행된다.
    # The fail-fast behavior comes from those pending or parallel requests exceeding the circuit-breaking thresholds. 
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 18
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
    
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

    • 503 UO (Upstream Overflow) : Envoy가 업스트림 서버로의 요청을 처리할 수 없어 오버플로우 발생 - 최대 연결/요청 제한 or 서킷 브레이커
      • 요청을 처리하려 했으나, Envoy 내부의 버퍼나 큐(예: 연결 풀, 요청 큐 등)가 가득 차서 더 이상 업스트림으로 요청을 전달할 수 없는 상태
        • 업스트림 서버가 느리게 응답하거나, 다운됨
        • 트래픽 급증으로 인해 큐에 쌓인 요청이 너무 많음
        • Envoy의 circuit breaker 설정 (예: max_requests, max_connections)을 초과함
    최종 return : 500 code

     

    2) Jaeger 확인

     : 실패 trace 확인 - simple-backend istio-proxy 가 UO 로 503을 리턴하고 → simple-web은 500을 사용자에게 최종 리턴

     

     

    jaeger 에서 Tags 필터링 찾기 : error=true


    3) 프로메테우스 모니링

    - 프로메테우스 메트릭 링크 envoy_cluster_upstream_cx_overflow - Link

     

     - 프로메테우스 메트릭 링크 envoy_cluster_upstream_rq_pending_overflow - Link


    ☞ 병렬로 발생하는 요청(현재 로드테스트 동시 요청 2)을 더 처리하고자 http2MaxRequests(parallel requests)를 늘려보자  

     

    # 설정 전 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                        "maxRequests": 1,
                        "maxRequestsPerConnection": 1
    
    # http2MaxRequests 조정: 1 → 2, '동시요청 처리개수'를 늘림
    kubectl patch destinationrule simple-backend-dr -n istioinaction \
    -n istioinaction --type merge --patch \
    '{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 2}}}}}'
    
    # 설정 후 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxRequests 
                        "maxRequests": 2,
                        "maxRequestsPerConnection": 1
    
    # istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # 로그 확인 : simple-web >> 아래 500(503) 발생 로그 확인
    kubectl logs -n istioinaction -l app=simple-web -c istio-proxy -f
    ... 
    ## jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13
    [2025-04-22T03:55:22.424Z] "GET / HTTP/1.1" 503 UO upstream_reset_before_response_started{overflow} - "-" 0 81 0 - "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-backend:80" "-" outbound|80||simple-backend.istioinaction.svc.cluster.local - 10.200.1.137:80 172.18.0.1:0 - -
    [2025-04-22T03:55:22.410Z] "GET / HTTP/1.1" 500 - via_upstream - "-" 0 688 15 15 "172.18.0.1" "fortio.org/fortio-1.69.3" "3e1789ba-2fa4-94b6-a782-cfdf0a405e13" "simple-web.istioinaction.io:30000" "10.10.0.18:8080" inbound|8080|| 127.0.0.6:43259 10.10.0.18:8080 172.18.0.1:0 outbound_.80_._.simple-web.istioinaction.svc.cluster.local default
    ...
    
    # 로그 확인 : simple-backend >> 503 에러가 발생하지 않았다??? 
    kubectl logs -n istioinaction -l app=simple-backend -c istio-proxy -f
    
    
    # 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 동시요청 처리개수가 기존 1 에서 2로 증가되어서 거의 대부분 처리했다. >> 참고로 모두 성공 되기도함.
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 3 (for perfect keepalive, would be 2)
    Code 200 : 33 (97.1 %)
    Code 500 : 1 (2.9 %)
    All done 34 calls (plus 2 warmup) 1789.433 ms avg, 1.1 qps
    ...
    
    # 'cx_overflow: 40' 대비 'rq_pending_overflow: 1' 가 현저히 낮아짐을 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 40
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 1
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
    
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

    • 요청 일부가(위에서는 1) 보류 대기열 서킷 브레이커를 작동시켰을 가능성이 있다.
    • What likely happened is that some requests tripped the pending queue circuit breaker.
    • kiali 확인 : 실제 simple-web 입장에서는 500(503) 출력되었지만, simple-backend 에는 503이 없다….

    jaeger 에서 Tags 필터링 찾기 : guid:x-request-id=3e1789ba-2fa4-94b6-a782-cfdf0a405e13 , guid:x-request-id=304fd07c-0d09-9749-8e36-c0758c7464e3

     

    • simple-backend 에서는 정상 301 응답을 주었고, simple-web 에서 503 (UO)발생 되었다. 503발생 주체는 simple-web istio-proxy인가??

     

    • 보류 대기열 깊이를 2로 늘리고 다시 실행해보자. Let’s increase the pending queue depth to 2 and re-run
    # http1MaxPendingRequests : 1 → 2, 'queuing' 개수를 늘립니다
    kubectl patch destinationrule simple-backend-dr \
    -n istioinaction --type merge --patch \
    '{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 2}}}}}'
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests 
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json | grep maxPendingRequests           
                        "maxPendingRequests": 2,
                        
    # istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    
    # 2개의 커넥션에서 요청을 초당 하나씩 보내기 : 모두 성공!
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 2 (for perfect keepalive, would be 2) # 큐 길이 증가 덕분에, 소켓을 2개만 사용했다.
    Code 200 : 33 (100.0 %)
    All done 33 calls (plus 2 warmup) 1846.745 ms avg, 1.1 qps
    ...
    
    # 'cx_overflow가 45이 발생했지만, upstream_rq_pending_overflow 는 이다!
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep overflow
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_overflow: 45
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_cx_pool_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_pending_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
    
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

     

    • 서킷 브레이커가 발동되면 통계를 보고 무슨 일이 일어났는지 확인 할 수 있다. 그런데 런타임은 어떤가?
    • 우리 예제에서는 simple-web → simple-backend 를 호출한다. 그런데 서킷 브레이커 때문에 요청이 실패한다면, simple-web 은 그 사실을 어떻게 알고 애플리케이션이나 네트워크 장애 문제와 구별할 수 있는가?

     

    • 요청 서킷 브레이커 임계값을 넘겨 실패하면, 이스티오 서비스 프록시는 x-envoy-overloaded 헤더를 추가한다.
    • 이를 테스트하는 한 가지 방법은 커넥션 제한을 가장 엄격한 수준으로 설정하고(커넥션, 보류 요청, 최대 요청을 1로 설정함 1 for connections, pending requests, and maximum requests) 로드 테스트를 다시 수행해보는 것이다.
    • 로드 테스트를 실행하는 도중에 단일 curl 명령도 실행하면 서킷 브레이커 때문에 실패할 가능성이 높다.
    # 
    kubectl patch destinationrule simple-backend-dr \
    -n istioinaction --type merge --patch \
    '{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http1MaxPendingRequests": 1}}}}}'
    
    kubectl patch destinationrule simple-backend-dr -n istioinaction \
    -n istioinaction --type merge --patch \
    '{"spec": {"trafficPolicy": {"connectionPool": {"http": {"http2MaxRequests": 1}}}}}'
    
    # 설정 적용 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-backend-1.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
    
    # istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # 로드 테스트
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    
    # 로드 테스트 하는 상태에서 아래 curl 접속 
    curl -v http://simple-web.istioinaction.io:30000
    {
      "name": "simple-web",
      "uri": "/",
      "type": "HTTP",
      "ip_addresses": [
        "10.10.0.18"
      ],
      "start_time": "2025-04-22T04:23:50.468693",
      "end_time": "2025-04-22T04:23:50.474941",
      "duration": "6.247ms",
      "body": "Hello from simple-web!!!",
      "upstream_calls": [
        {
          "uri": "http://simple-backend:80/",
          "headers": {
            "Content-Length": "81",
            "Content-Type": "text/plain",
            "Date": "Tue, 22 Apr 2025 04:23:50 GMT",
            "Server": "envoy",
            "X-Envoy-Overloaded": "true" # Header indication
          },
          "code": 503,
          "error": "Error processing upstream request: http://simple-backend:80//, expected code 200, got 503"
        }
      ],
      "code": 500
    }

     

    • tcpdump 후 wireshark 확인 : 흠.. 현재 실습 동작에서는 http 헤더에 추가되는 것이 아닌 것 같다… - Github
    • 일반적으로 네트워크가 실패할 수 있다는 점을 감안해 애플리케이션 코드를 작성해야 한다.
    • 애플리케이션 코드가 이 헤더를 확인하면 호출한 클라이언트에게 응답을 보내기 위해 대체 전략 fallback 을 사용하는 결정을 내릴 수 있다.

     

    6.5.2 Guarding against unhealthy services with outlier detection* : 이상값 감지로 비정상 서비스에 대응하기

     

    • 앞 절에서는 서비스에 예기치 못한 지연 시간이 있을 때 오동작하는 서비스로의 요청을 이스티오가 어떻게 제한할 수 있는지 살펴봤다.
    • 이번 절에서는 오동작 misbehaving 하는 특정 호스트를 서비스에서 제거하는 이스티오의 접근법을 다룬다.
    • 이스티오는 이를 위해 엔보이의 이상값 감지 기능을 사용한다. Istio uses Envoy’s outlier-detection functionality for this.

     

    [ 실습 환경 초기화 ]

    더보기
    • 동작을 살펴보기 위해 이스티오의 기본 재시도 메커니즘도 비활성화 한다.
    • 재시도와 이상값 감지는 잘 어울리지만, 이 예제에서는 이상값 감지 기능을 고립시키려고 한다.
    • 재시도는 마지막에 추가해서 이상값 감지와 재시도가 서로 어떻게 보완하는지 확인해본다.
    #
    kubectl delete destinationrule --all -n istioinaction
    kubectl delete vs simple-backend-vs -n istioinaction
    
    # disable retries (default) : 이미 적용 되어 있음
    docker exec -it myk8s-control-plane bash
    ----------------------------------------
    istioctl install --set profile=default --set meshConfig.defaultHttpRetryPolicy.attempts=0
    y
    exit
    ----------------------------------------
    
    #
    kubectl apply -f ch6/simple-backend.yaml -n istioinaction
    kubectl apply -f ch6/simple-web-stats-incl.yaml -n istioinaction # 통계 활성화
    
    # istio-proxy stats 카운터 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # 호출 테스트 : 모두 성공
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    
    # 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

     

     

    [ 부하 주면서 모니터링 하기 ]

    더보기

    1. simple-backend-1 엔드포인트는 호출 중 75%가 HTTP 500 실패 설정 배포 및 확인

    #
    kubectl apply -n istioinaction -f ch6/simple-backend-periodic-failure-500.yaml
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- env | grep ERROR
    
    #
    kubectl exec -it deploy/simple-backend-1 -n istioinaction -- sh
    ---------------------------------------------------------------
    export ERROR_TYPE=http_error
    export ERROR_RATE=0.75
    export ERROR_CODE=500
    exit
    ---------------------------------------------------------------
    
    # 정보 확인
    kubectl get deploy,pod -n istioinaction -o wide
    NAME                               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS       IMAGES                                 SELECTOR
    deployment.apps/simple-backend-1   1/1     1            1           20h   simple-backend   nicholasjackson/fake-service:v0.14.1   app=simple-backend
    deployment.apps/simple-backend-2   2/2     2            2           20h   simple-backend   nicholasjackson/fake-service:v0.17.0   app=simple-backend
    deployment.apps/simple-web         1/1     1            1           21h   simple-web       nicholasjackson/fake-service:v0.17.0   app=simple-web
    
    NAME                                   READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
    pod/simple-backend-1-bdb6c7ff8-rqqlr   2/2     Running   0          2m25s   10.10.0.30   myk8s-control-plane   <none>           <none>
    pod/simple-backend-2-6799f8bf-d4b6t    2/2     Running   0          11m     10.10.0.27   myk8s-control-plane   <none>           <none>
    pod/simple-backend-2-6799f8bf-dk78j    2/2     Running   0          11m     10.10.0.29   myk8s-control-plane   <none>           <none>
    pod/simple-web-865f4949ff-56kbq        2/2     Running   0          3h32m   10.10.0.18   myk8s-control-plane   <none>           <none>
    
    
    # 로드 테스트 실행 : 재시도를 끄고, backend-1 엔드포인트에 주기적인 실패를 설정했으니, 테스트 일부는 실패
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 19 (for perfect keepalive, would be 2)
    Code 200 : 43 (71.7 %)
    Code 500 : 17 (28.3 %)
    All done 60 calls (plus 2 warmup) 134.138 ms avg, 2.0 qps
    ...
    
    # 통계 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream

     

    • 정기적으로 실패하는 서비스에 요청을 보내고 있는데 서비스의 다른 엔드포인트들은 실패하지 않고 있다면, 해당 엔드포인트가 과부하됐거나 어떤 이유로든 성능이 저하된 상태일 수 있으므로 당분간 그 엔드포인트로 트래픽을 전송하는 것을 멈춰야 한다.
    • 이상값 감지를 설정해보자 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다. - Docs
      • consecutive5xxErrors: 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5, 연속적인 에러 횟수 threshold
      • interval: 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
      • baseEjectionTime: 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime.
        • 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초.
      • maxEjectionPercent: 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%).
        • 100% 설정 시모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%
    #
    cat ch6/simple-backend-dr-outlier-5s.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 # 잘못된 요청이 하나만 발생해도 이상값 감지가 발동. 기본값 5
          interval: 5s # 이스티오 서비스 프록시가 체크하는 주기. 기본값 10초. Time interval between ejection sweep analysis
          baseEjectionTime: 5s # 서비스 엔드포인트에서 제거된다면, 제거 시간은 n(해당 엔드포인트가 쫓겨난 횟수) * baseEjectionTime. 해당 시간이 지나면 로드 밸런싱 풀에 다시 추가됨. 기본값 30초. 
          maxEjectionPercent: 100 # 로드 밸런싱 풀에서 제거 가능한 호스트 개수(%). 모든 호스트가 오동작하면 어떤 요청도 통과 못함(회로가 열린 것과 같다). 기본값 10%
    
    kubectl apply -f ch6/simple-backend-dr-outlier-5s.yaml -n istioinaction
    kubectl get dr -n istioinaction
    
    #
    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": "5s",
                "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'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    
    
    
    # 통계 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
    	while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done
    	ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.30:8080     HEALTHY     FAILED            outbound|80||simple-backend.istioinaction.svc.cluster.local
    
    
    # 로드 테스트 실행 : 기존 오류율 대비 극적으로 감소. 오동작하는 엔드포인트를 잠시 제거했기 때문이다.
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 5 (for perfect keepalive, would be 2)
    Code 200 : 58 (96.7 %)
    Code 500 : 2 (3.3 %)
    All done 60 calls (plus 2 warmup) 166.592 ms avg, 2.0 qps
    ...
    
    # 통계 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream
    
    # 엔드포인트 이상 감지 전에 3번 실패했고, 이상 상태가 되고 나면 로드 밸런서 풀에서 제거되어서 이후 부터는 정상 엔드포인트로 호출 응답됨.
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep outlier
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_active: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_consecutive_5xx: 3
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_5xx: 3
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_gateway_failure: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_consecutive_local_origin_failure: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_failure_percentage: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_failure_percentage: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_local_origin_success_rate: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_detected_success_rate: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_5xx: 3
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_gateway_failure: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_consecutive_local_origin_failure: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_failure_percentage: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_failure_percentage: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_local_origin_success_rate: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_success_rate: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_enforced_total: 3
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_success_rate: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.outlier_detection.ejections_total: 3
    
    
    # 5초 후(baseEjectionTime: 5s) 다시 엔드포인트 모니터링
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.27:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.29:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local
    10.10.0.30:8080     HEALTHY     OK                outbound|80||simple-backend.istioinaction.svc.cluster.local

     

    simple-backend-1 에서 500 리턴

     


    2. 오류율 개선해 보기

    • 오류률을 더 개선해보자. 기본 재시도 설정을 추가해보자. VirtuslService 에 명시적으로 설정 가능. (현재 mesh 기본 재시도 0 상태)  
    #
    cat ch6/simple-backend-vs-retry-500.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: simple-backend-vs
    spec:
      hosts:
      - simple-backend
      http:
      - route:
        - destination:
            host: simple-backend
        retries:
          attempts: 2
          retryOn: 5x
    
    kubectl apply -f ch6/simple-backend-vs-retry-500.yaml -n istioinaction
    
    # 통계 초기화
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
    -- curl -X POST localhost:15000/reset_counters
    
    # 엔드포인트 모니터링 먼저 해두기 : 신규 터미널
    while true; do docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' ; date; sleep 1; echo; done
    
    # 로드 테스트 실행 : 모두 성공!
    fortio load -quiet -jitter -t 30s -c 2 -qps 2 --allow-initial-errors http://simple-web.istioinaction.io:30000
    ...
    Sockets used: 2 (for perfect keepalive, would be 2)
    Code 200 : 60 (100.0 %)
    All done 60 calls (plus 2 warmup) 173.837 ms avg, 2.0 qps
    ...
    
    # 엔드포인트 이상 감지 전에 3번 실패했지만, 재시도 retry 덕분에 결과적으로 모두 성공!
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend | grep outlier
    
    # 통계 확인
    kubectl exec -it deploy/simple-web -c istio-proxy -n istioinaction \
     -- curl localhost:15000/stats | grep simple-backend.istioinaction.svc.cluster.local.upstream | grep retry
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry: 4
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_exponential: 4
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_backoff_ratelimited: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_limit_exceeded: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_overflow: 0
    cluster.outbound|80||simple-backend.istioinaction.svc.cluster.local.upstream_rq_retry_success: 4

     

    ☞ jaeger 확인 : 아래 처럼 재시도해서 최종적으로 사용자 입장에서는 정상 응답

    로드 테스트를 다시 실행하면 오류가 없음을 확인할 수 있다.

     

     


    • 이번 장 이전에는 이스티오의 기능과 API를 사용해, 인그레스 게이트웨이를 사용한 예지에서부터 클러스터 내 통신에 이르기까지 네트워크의 동작을 바꾸는 방법을 살펴봤다.
    • 그러나 이번 장의 서두에서 언급했듯이, 끊임없이 변화하는 대규모 시스템에서 예상치 못한 네트워크 오류에 대응하기 위한 수동적인 개입은 불가능에 가까운 것이다.
    • 이번 장에서의 이스티오의 다양한 클라이언트 측 복원력 기능을 깊이 들여다봤다.
    • 이 기능들은 서비스가 간헐적인 네트워크 문제나 토폴로지 변화로부터 투명하게 복구될 수 있도록 돕는다.
    • 다음 장에서는 이런 기능을 더해 네트워크 동작을 관찰하는 방법을 살펴볼 것이다.

     

     

    [ Summary ]

    더보기
    • 로드 밸런싱DestinationRule 리소스로 설정한다. 지원하는 알고리듬은 다음과 같다.
      • ROUND_ROBIN은 요청을 엔드포인트에 차례대로(or next-in-loop) 전달하며 기본 알고리듬이다.
      • RANDOM은 트래픽을 무작위 엔드포인트로 라우팅한다.
      • LEAST_CONN은 진행 중인 요청이 가장 적은 엔드포인트로 트래픽을 라우팅한다.
    • 이스티오는 노드의 영역 및 리전 정보를 엔드포인트 상태 정보(outlierDetection 이 설정돼 있어야 함)와 함께 활용해 트래픽을 동일 영역 내의 워크로드로 라우팅한다. (가능한 경우 그렇게 하고, 그렇지 않을 경우 다음 영역으로 넘긴다)
    • DestinationRule 를 사용하면 클라이언트가 여러 지역에 가중치를 부여해 트래픽을 분배하도록 설정할 수 있다.
    • 재시도와 타임아웃은 VirtualService 리소스에서 설정한다.
    • EnvoyFilter 리소스를 사용하면 이스티오 API가 노출하지 않은 엔보이의 기능을 구현할 수 있다. 요청 헤징으로 이를 보여줬다.
    • 서킷 브레이커는 DestinationRule 리소스에서 설정하는데, 이 기능은 트래픽을 더 전송하기 전에 업스트림 서비스가 회복할 시간을 벌어준다.

     


    [ 중요 - 실습 후 자원정리 ]

    ## 1. cluster 삭제
    kind delete cluster --name myk8s 
    
    ## 2. /etc/hosts 파일에 추가했던 도메인 제거
    vi /etc/hosts

     

     


    [ 마무리 ]

    istio proxy 가 가진 다양한 기능을 통해 운영/개발 등 필요한 환경에서 최소한의 effort 으로 서비스 품질에 대한 보상과 개선을 할 수 있다는 점이 놀랍고 흥미로웠다. 부하 모델 테스트 시, 강의 시간 정리한 내용과 Random 모델의 결과가 상이하여 좀 당황하였지만, Least Connection이 가장 좋은 성능의 부하 분산 모델이라는 점에서는 이전 업무 경험을 근간으로 동의한다. 추후 좀더 여유를 가지고 documents 등 참조를 통해 업무에 활용할 수 있는 부분을 검토해 보아야 겠다는 생각이 든다.


    [ 도전과제 모음 ]

     

    [도전과제1] Flagger 공식문서에 주요 기능 실습 및 정리 - Docs

    더보기
    • Flagger 배포 전략 실습 : A/B Testing, Blue/Green Deployments , Canary with Session Affinity - Docs
      • Blue/Green Deployments - Docs
      • Istio Canary Deployments - Docs
      • Istio A/B Testing - Docs
    • 무중단 배포 Zero downtime deploymentes - Docs

    [도전과제2] Istio 공식문서에 Egress 내용을 실습 및 정리 - Docs

    더보기
    • Accessing External Services - Docs
    • Egress Gateways - Docs
    • Kubernetes Services for Egress Traffic - Docs
    • Configuring Gateway Network Topology - Docs

    [도전과제3] Istio ServiceEntry 에 경계 설정 해보기 (키알리에서 확인), 그외 활용 방안들 실습 해보고 정리 - Docs

    [도전과제4] Istio Sidecar 내용 정리와 예시실습 해보고 정리 - Docs , Istio Sidecar Object를 활용한 Sidecar Proxy Endpoint 제어 - Blog

    [도전과제5] Istio 공식문서에 DestinationRule 내용을 실습 및 정리 - Docs

    [도전과제6] Istio 공식문서에 Envoy Filter 내용을 실습 및 정리 - Docs

     

     


    [ 참조 링크 모음 ]