새소식

인기 검색어

Service_Mesh/Istio

[Istio-3주차] Traffic control (이론, 실습)

  • -

5장 Traffic control: Fine-grained traffic routing

들어가며

이번 장에서는? VirtualService 로 실질적이고 세밀한 라우팅을 구현한다.

  • 요청이 클러스터 인입 시 요청 처리를 위해 적절한 서비스로 라우팅하는 방안
  • 클러스터 내 서비스 - 같은 클러스터 내 또는 클러스터 외부에 있는 타 서비스와 통신 방안
  • 고객이 새 버전 도입 시 안전하게 노출되며 영향도 최소화하는 방안
  • 복잡한 애플리케이션 간 트래픽을 세밀하게 제어하는 방안

Istio 트래픽 관리

단계 도구 주요 기능 효과
트래픽 수용 Gateway L7 라우팅 규칙 적용 인그레스 라우팅 세부화 
내부 라우팅 VirtualService 카나리아 배포 롤백 시간 개선 (ex. 5분 → 30초)
외부 연동 ServiceEntry 외부 API 연결 표준화 API 응답 시간 개선
보안 강화 Sidecar mTLS 자동 구성 보안 인시던트 감소
  • Gateways.Mesh: Gateways(예: IngressGateway)는 Mesh의 경계에서 외부 트래픽을 수용하거나 내보내는 역할을 하며, Mesh 내부 서비스와 외부 세계를 연결하는 진입,출구 포인트를 제공
  • VirtualService: Istio에서 특정 트래픽 조건에 따라 서비스로의 라우팅 경로와 정책을 세밀하게 정의.
  • ServiceEntry: 외부 서비스나 기존 서비스 레지스트리에 없는 엔드포인트를 Istio 서비스 레지스트리에 수동으로 등록해 메쉬 내에서 접근,제어할 수 있게 함
  • Sidecar: Sidecar는 워크로드 인스턴스에 프록시(Envoy)를 부착해, 해당 워크로드의 인/아웃바운드 트래픽을 제어하고 필요에 따라 네트워크 정책 범위를 제한할 수 있게 해줌

 

신규 애플리케이션 배포 위험 줄이기

  • blue / green deployments: 초기에 시도 가능한 패턴
    • Blue: v1 / Green: v2
      1. Blue(v1) 옆에 Green(v2) 배포
      2. 출시 시점: Green(v2) 로 트래픽을 Cutover
    • 점진적으로 Blue(v1) 대상 트래픽을 줄일 수 있어, 배포 중 중단을 줄이는 데 도움
    • 다만 v1에서 v2로 전환 시 여전히 모든 코드 변경 사항을 한 번에 해제하는 "빅뱅"을 경험
    • 먼저, 배포와 릴리스가 무엇인 지 명확히 해야 함.
 

배포 Deployment vs 릴리스 release

  • 배포와 릴리스의 차이점?
  • 예시) catalog 서비스: v1 (운영 중)

  • 배포 - Deployment
    • 새 코드를 운영 환경 리소스(서버,컨테이너 등) 에 설치하지만, 트래픽을 전송하지 않는 상태
      Deployment: 운영 환경에 설치되지만 실제 운영 환경 트래픽을 받지는 않는 코드. (운영 환경에 설치되면 smoke 테스트를 수행/검증)


    • 배포 시 과정 (서비스에서 코드 변경)
      1. CI 시스템으로 Build
      2. 새 버전으로 태그 (ex. v1.1)
      3. 스테이징 환경에 배포 및 테스트
    • 배포 과정을 진행 시 (스테이징에서 검증 + 승인 절차 이후) 
      • 운영 환경에 v1.1 신규 설치
      • 이 때, v1.1 에 트래픽은 전송하지 않음
        • 어떤 사용자 요청도 받지 않은 상태로, 따라서 사용자에 영향을 미치지 않아야 함.
    • 배포의 목적
      1. 새 배포에 테스트 실행 + 기대에 맞게 작동하는 지 검증하는 시점
      2. 메트릭과 로그 수집을 통해 기대값 작동에 대한 '확신'이 가능
      3. 릴리즈 관련 방안에 대해 비즈니스 의사 결정이 가능
  • 릴리즈 - Release
    • 실제 트래픽을 새 배포로 가져오는 것
      Release: 운영 환경 트래픽을 배포로 이관하는 순간을 말함. 이상적으로는 점진적으로 이뤄져야 함


    • 릴리즈의 목적
      • 내부 직원은 트래픽을 제어해 신 버전 사용 가능
      • 개발관리자는 로그와 메트릭 수집을 활용, 코드 변경 효과가 의도대로인지 관찰 및 검증 가능
    • Canary 릴리즈
      • 트래픽: 소프트웨어 구 버전(대부분) / 신 버전 (일부)
      • 신 버전을 노출할 소수의 사용자 그룹 선택 후 동작 점검
        • 신 버전 동작/성능 이상 시: 릴리즈 철회 및 트래픽 구 버전으로 재전환
        • 신 버전 동작/성능 양호 시: 릴리즈 범위 넓힘 (대상 고객 확장, 우선순위 낮은 고객)
          내부사용자거나 레벨이 silver인 사용자 대상으로 확대됨
        • 새 코드를 모든 고객에게 노출할 때까지 릴리스하고 관찰하는 접근법 계속 반복
        • 각 과정 중 기대대로 코드가 동작,성능 전달 불가한 시점이 있을 시 그 즉시 롤백 가능(트래픽을 이전 버전으로)

  • 결론
    • 배포와 릴리스를 분리하면 새 버전의 코드를 어떤 사용자들에게, 어떻게 내보일지 정밀하게 제어할 수 있음
    • 이를 통해 새 코드를 운영 환경에 가져오는 위험성을 줄일 수 있음
    • Istio 또한 시스템에 들어오는 요청을 기반으로 트래픽을 제어함으로써 릴리스 위험성을 낮추는 데 도움이 됨. (실습)

 


5.2 Routing requests with Istio

        • 이전 포스트: Istio를 사용하여 카탈로그 서비스로의 트래픽 제어 (Istio VirtualService 를 통한 트래픽 라우팅)
        • 작동 방식 상세
          • 요청의 내용에 따라 헤더를 평가하여 요청의 경로를 제어
            • 다크 런치: 특정 사용자에게 배포를 제공. 많은 사용자가 현재 공식 제공 중인 서비스 버전으로 라우팅, 특정 클래스의 사용자는 최신 버전으로 라우팅)
            • 효과: 대부분 사용자에게 영향을 미치지 않고 특정 그룹에 통제된 방식으로 새로운 기능 노출
        • 실습
          1. 5.2.1: 이전 실습 내용 삭제
            • 이전 실습 내용 삭제
              # 4장 실습 리소스들 삭제
              kubectl delete deployment,svc,gateway,virtualservice,destinationrule --all -n istioinaction
          2. 5.2.2: catalog v1 서비스 배포
            1. v1 카탈로그 서비스 배포
              # 카탈로그 서비스 v1을 배포 (책 소스 코드 root에서 다음 명령을 실행)
              kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
              
              # 확인 (파드 1개가 배포)
              kubectl get pod -n istioinaction -owide
              NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
              catalog-6cf4b97d-5nddg   2/2     Running   0          77s   10.10.0.18   myk8s-control-plane   <none>           <none>
              
              
              # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
              echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
              cat /etc/hosts | tail -n 2
              
              ## 윈도우에서 hosts 파일에 catalog 도메인 추가 (Powershell)***
              Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "127.0.0.1       catalog.istioinaction.io"
              Get-Content $env:SystemRoot\System32\drivers\etc\hosts | Select-Object -Last 2
              
              # netshoot로 내부에서 catalog 접속 확인 -> default namespace 에서 서비스명으로 접근한 것.
              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 리소스 설정
              # Gateway 다음에 VirtualService를 붙여야 외부에서 접근한 list를 라우팅해서 접근 가능
              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
              
              # 확인1
              kubectl apply -f ch5/catalog-vs.yaml -n istioinaction
              
              # 확인2
              kubectl get gw,vs -n istioinaction
              
              NAME                                          AGE
              gateway.networking.istio.io/catalog-gateway   59s
              
              NAME                                                    GATEWAYS              HOSTS                          AGE
              virtualservice.networking.istio.io/catalog-vs-from-gw   ["catalog-gateway"]   ["catalog.istioinaction.io"]   31s
              
              
              # istio-ingressgateway Service(NodePort)에 포트 정보 확인 - 사용하는 포트는 노드포트. 
              kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
              [
                {
                  "name": "status-port",
                  "nodePort": 30371,
                  "port": 15021,
                  "protocol": "TCP",
                  "targetPort": 15021
                },
                {
                  "name": "http2",
                  "nodePort": 30000,  # 인입
                  "port": 80,			# 내부에서 호출할 때 (ex. netshoot)
                  "protocol": "TCP",
                  "targetPort": 8080  # 인입된 트래픽 타겟포트로 보냄
                },
                {
                  "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
              
              
              # 신규 터미널 : 반복 접속 실행 해두기 (kiali로 계속 화면을 볼 것)
              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
               
              • 결과 화면
              • 윈도우용 hosts 설정 및 확인.


              • 트래픽 경로 확인: curl(외부) → ingressgateway → virtualservice(catalog v1)

              • 참고) envoy 트래픽 처리 단계

                동작 과정(Downstream to Upstream) 및 Envoy 구성요소



                • 동작 흐름: 다운스트림 → 업스트림 (클라이언트에서 수신한 다운스트림을 업스트림으로)
                • 구성 요소(4가지): 반복되는 용어들이니 숙지 필요)
                  1. Listeners
                  2. Routes
                  3. Clusters
                  4. Cluster subsets (안의 Endpoint를 가리킴 - 사진 참고)
              • 고급 재시도 - envoy Istio API 확장 필터 체인 사용


            2. catalog v1 서비스의 istio proxy sidecar 및 각 xDS 현황 확인
              # catalog v1 배포 상태에서 istio proxy로 동작하는 sidecar 확인
              ## catalog 및 istio-ingressgateway 2개에 프록시가 붙은 상태로 확인이 됨
              docker exec -it myk8s-control-plane istioctl proxy-status
              NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
              catalog-6cf4b97d-tb92w.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-4ht5b     1.17.8
              istio-ingressgateway-996bc6bb6-78vrs.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-4ht5b     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
              ## istio ingressgateway에서도 확인 가능하지만, catalog 서비스에서 sidecar proxy로도 배포가 되어있다.
              ## 따라서 다음 설정으로도 동일하게 확인이 가능.
              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

              istioctl: -h 로 필터링 할 수 있는 옵션들 확인 가능
              --port : 포트로 제한 (ex. 8080)
              -o : output (상세히 보고싶다면 json. json 이 실제 configuartion)

              LDS - Listener Discovery Service

              RDS - Route Discovery Service

              CDS - Cluster Discovery Service
              클러스터 정보를 보고싶은데 너무 많음. -fqdn 으로 필터링 가능

              EDS - Endpoint Discovery Service
              필터링: direction(in/out)|port|fqdn(service)

              위 내용은 ingressgateway 관련 설정을 확인하였으나,
              동일하게 catalog sidecar proxy가 배포된 istio proxy config에서도 확인 가능



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

              proxy의 config를 명령어로 보기 불편하다고 하면 port-forwarding 시킨다음에 admin web을 접속하여 확인할 수 있다.
              1. cert: 인증서 내용
              2. config_dump 바로 확인 가능
              3. help
              4. heap_dump
              5. listener
              6. prometheus 포맷의 메트릭도 실시간으로 확인 가능
          3. 5.2.3 - catalog v2 서비스 배포
            1. istio 트래픽 제어 기능 동작을 알아보기 위해, catalog 서비스 v2 를 배포해보기
              # catalog 서비스 v2 를 배포 : v2에서는 imageUrl 필드가 추가
              kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
              
              # 배포 상태 확인 (v1, v2) app=catalog 라벨은 동일, version v1,v2 태그만 다름.
              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>
              
              # catalog v2 도 istio proxy에 상태 편입
              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
              
              # kiali에서 트래픽 부하분산 확인하기 위한 호출 테스트
              # 호출 테스트 : 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 로도 확인 가능
              docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction --cluster "outbound|80||catalog.istioinaction.svc.cluster.local" -o json

              v1, v2 부하분산 확인
              어떻게 부하분산이 되는가?
              지금 현재 istio proxy는 istio-ingressgateway에서 뒷단에 catalog에 연결하고, catalog에 있는 istio proxy에 연결하는 상황

              istio-ingressgateway의 cluster 정보를 보면, type: EDS 로 되어있다.
              cluster 에 clusterset, endpoint가 묶여있다. LB Policy는 Least Request 다.

              EDS의 동작을 확인하기위해 istio-ingressgateway를 cluster로 필터링 걸어서 보면 Pod IP가 확인된다.
              endpoint로 확인
              pod로 확인
              이 Pod IP를 어떻게 Istio가 가지고 갈까? 갤리가 k8s API를 호출해서 서비스나 엔드포인트 정보를 동적으로 모니터링해서 가져간다. (istio controleplane 에 clusterrole 보면 정보를 가져갈 수 있는 권한이 있다.)
              servic,endpoint 확인


              endpoint도 디테일하게 보고 싶다면, json으로 출력한다.
              endpoint name별로 host가 리스트로 두개 들어 있다. (address, stats)
              address: Pod IP 및 Port. Pod 및 Endpoint IP와 부합
              stats: 총 요청개수, 리퀘스트 성공 여부 등 통계치 등 확인 가능하다.

              이외에, circuitbreaker 정보 (thresholds) 등을 확인할 수 있다.

              번외: istio proxy 명령어로도 확인이 가능
          4. 5.2.4 catalog v1 서비스로 모든 트래픽을 라우팅하기
            • 현재는 별도의 설정이 없어 v1, v2로 번갈아가며 라우팅이 되고 있다. 
            • 신규(v2) 버전은, 예를 들어 QA팀이 테스트를 안해서 사용자 경험이 안좋다. (버그 등)
            • 다크런치라는 트래픽 패턴을 도입
              • 기존버전(v1)으로 기본적으로 모든 트래픽 송신
              • 일부 사용자만, 특정 경로(v2)로 트래픽 송신
              • 어느 워크로드가 v1, v2 인지 이스티오에게 힌트를 줘야 함
                • catalog v1은 deployment 리소스에서 레이블 app:catalog, version:v1 을 사용한다.
                • catalog v2은 deployment 리소스에서 레이블 app:catalog, version:v2 을 사용한다.
              • 이를 DestinationRule 등으로 부분집합 (subset) 설정 가능

                # catalog Pod의 라벨 확인 (istio 가 만든 일부 label 도 있음 주의)
                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
                
                # DestinationRule 로 부분집합 만들기
                ## 두개가 있음 (subset으로, 어떤 Pod의 어떤 라벨이 v1 or 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
                
                # destionationrule(dr) 배포
                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",
                ...
                
                # subset 없는 cluster도 확인 - 해당 endpoint로 들어오면 v1,v2로 부하분산
                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"
                                }
                            }
                        },
                ...


                catalog pod 레이블 확인
                catalog 서비스용 DestinationRule(dr) 배포

                catalog proxy-config 확인 : SUBSET(v1, v2, -) 확인
                업데이트된 사항
                Cluster라는 부분에 기존에 없던 2가지의 라인이 추가됨.
                SUBSET 추가(version-v1, version-v2)

                catalog v1 proxy-config 상세 (-o json) 확인: name부분 3번째에 subset 정보 추가
                catalog v2 proxy-config 상세 (-o json) 확인

                없는 버전의 클러스터 확인 - pod version v1,v2 부하분산

                subset 정보 추가 후, endpoint 항목이 2가지 추가. (ip 정보는 동일)

                없는 버전의 클러스터명으로 필터링하면, v1, v2 두가지 버전 아직 사용 중임을 확인할 수 있다.

                그래서 이 config들을 istio에 세팅을 하나하나 하게 되면 envoy에 설정되는데
                envoy 어느 곳에 설정되는 지 확인해본다면 이해할 수 있다.

                v1,v2 부하분산 확인 가능

                virtualservice Destination Rule 을 v1만 가도록 수정한다.

                트래픽이 v1 비중이 높아짐

                v1으로만 트래픽 전송
                이것은 어디에 적용되는가?
                Route 부분에 name이 http.8080인 항목을 output으로 확인하면 다음과 같다.
                virtualhost - routes - route의 cluster 정보에 subset(version-v1) 이 추가된 모습

                virtualhost - routes - route의 cluster 정보에 subset(version-v1) 이 추가된 모습이다.

                cluster subset version-v1 확인, metadata 정보에 destination-rule 정보 추가가 확인됨. subset 항목 또한 확인



              • 이 시점에서 모든 트래픽이 v1 라우팅 된다.
              • 이제 특정 요청들을 통제된 방식으로 v2 라우팅하고 싶다. (다음 절에서)
          5. 5.2.5 특정 요청들을 catalog v2 서비스로 보내기
            특정 콘텐츠가 담긴 요청 라우팅
              • HTTP 요청 헤더 x-istio-cohort: internal 을 포함한 트래픽(통제된 방식)은 catalog v2로 보내고 싶다.
                # 기존에 있던 virtualservice 수정
                ## http 항목에서 match, route 두 가지 방식이 있다.
                ## 방화벽처럼, 상단에 있는 것을 먼저 매치한다. 
                ## 따라서 좀 더 세부적인 설정은 상단에 배치하는 것이 좋다.
                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",
                ...
                
                # cluster 및 endpoint에서도 확인
                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...          /*

                virtualservice 수정 (route 설정)

                호출 테스트 1: 여전히 v1으로만 전송

                호출 테스트 2: 헤더 전송은 v2로 전송됨



                http.8080 내 동일한 내용이 2가지가 들어 있다.


                근데 실제 상세내용 (json)을 확인해보면 두개가 좀 다르다.

                routes 안에 리스트로 묶여 있는데,
                위의 route 매치는 header 매치되면 subset, version-v2로 보내라는 뜻.
                여기에 매치되지 않는 route는 version-v1으로 보낸다.



                istio-proxy (catalog)에는 routes 정보가 아래 cluster 로 보내는 1개만 있다. 즉 istio-proxy(istio-ingressgateway)가 routes 분기 처리하는 것을 알 수 있다.
          6. 5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)
            호출 그래프 내 깊은 위치에서 수행하는 특정 콘텐츠가 담긴 요청 라우팅
            • 지금까지 Istio를 사용해 요청을 라우팅하는 방법을 살펴봤지만, 라우팅 수행 위치가 Edge/Gateway 뿐이었다.
            • 이런 트래픽 규칙은 호출 그래프 내 깊은 곳에서도 적용할 수 있음 (그림 5.9 참조)
              • Istio proxy(sidecar, 여기서는 webapp service 에 위치) 에서도 라우팅 규칙 적용 가능
            • 프로세스를 다시 만들고 기대대로 동작하는지 확인하는 실습을 진행한다. (webapp to catalog)
              # 초기화
              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
              
              # 윈도우(Powershell) 도메인 질의 임시 설정
              Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "127.0.0.1       webapp.istioinaction.io"
              Get-Content $env:SystemRoot\System32\drivers\etc\hosts | Select-Object -Last 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으로 연결되었어요.
              ...



              Gateway, VirtualService 설정

              임시도메인 설정: wsl2

              임시도메인 설정: Windows(Powershell)
              호출테스트(webapp): : 외부(web, curl) → ingressgw → webapp → catalog (v1, v2)

              반복 호출테스트: 외부(web, curl) → ingressgw → webapp → catalog (v1, v2)

              route: virtualservice 생성 내용 확인
              =sidecar proxy 통신 가능한 fqdn 확인 가능
              webapp 쪽을 fqdn으로 확인: outbound 80

              엔드포인트 체크: webapp endpoint는 하나인것을 알 수 있다.
              로깅 활성화

              webapp istio-proxy 로그 활성화 적용
              로그 인입 및 webapp -> v1, v2 라우팅 확인 가능

              공유아이콘(점 3개)이 webapp은 edge에 붙어있으나 catalog 는 붙어있지 않다.


            • catalog v1 서비스로 모든 트래픽을 라우팅하는 VirtualService, DestinationRule 리소스를 만들기
              # destinationrule 다시 하나 추가
              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. subset 추가 확인
              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
              
              # endpoint 확인
              docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
              ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
              10.10.0.13:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
              10.10.0.13:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
              10.10.0.15:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
              10.10.0.15:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
              10.10.0.16:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
              
              # pod ip 확인
              kubectl get pods -n istioinaction -o wide
              NAME                          READY   STATUS    RESTARTS   AGE     IP           NODE                  NOMINATED NODE   READINESS GATES
              catalog-6cf4b97d-tb92w        2/2     Running   0          6h22m   10.10.0.13   myk8s-control-plane   <none>           <none>
              catalog-v2-6df885b555-xwxhf   2/2     Running   0          5h16m   10.10.0.15   myk8s-control-plane   <none>           <none>
              webapp-7685bcb84-fqh7n        2/2     Running   0          40m     10.10.0.16   myk8s-control-plane   <none>           <none>
              
              # catalog virtualservice에 mesh 설정 추가
              ## 지금까지는 edge에서, virtualservice 붙여서 라우팅 (hosts: catalog 설정밖에 없었음)
              ## 변경사항: mesh에 모든 sidecar도 마치 자기가 gateway처럼 트래픽을 꺾는 기능 추가 (gateways: mesh 추가 및 destination으로 전달)
              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


              virtualservice 설정 변경
              virtualservice 확인
              webapp-virtualservice: istio ingressgateway를 사용을 해서 내부로 트래픽을 라우팅할 때 쓰는 virtualservice이다.

              근데 방금전에 생성한 catalog virtualservice라는 항목도 있다.
              이는 gateway가 istio ingressgateway가 처리하는 게 아닌 mesh로 처리한다.
              istio 내부에 있는 모든 사이드카 항목들도 마치 gateway처럼 트래픽 라우팅을 해줄 수 있음을 의미한다.
              v2 트래픽 축소 확인
               공유아이콘(점 3개)이 catalog 에서도 확인
              v2 트래픽 제외 확인
              로그: v1으로 트래픽 처리 확인

              기존에 webapp 에서 catalog로의 virtualservice 정보가 없었는데 추가가 됨 (맨 뒤)
              v1 추가 확인


            • Mesh 라우팅이 동작하도록 적용해두었기 때문에 실제 설정을 적용
              # mesh 라우팅에 설정 추가
              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


              새로운 vs 설정 적용
              v2로도 라우팅 되는 것을 확인 가능 (kiali, log)
              catalog: DR, VS / webapp: Gateway, VS



 


5.3 Traffic Shifting

  • 들어가며
    • 이 절에서는 배포를 ‘카나리 canary’하거나 점진적으로 릴리스(incrementally release)하는 또 다른 방법을 살펴본다.
    • 이전 절에서는 헤더 비교를 기반해 특정 사용자 그룹에 다크 런치 dark launch 를 수행하는 라우팅을 살펴봤다.
    • 이 절에서는 가중치를 기반으로 특정 서비스의 여러 버전에 라이브 트래픽을 분배해본다.
    • 예를 들어 catalog 서비스 v2를 내부 직원에게 다크 런치했고 이 버전을 모두에게 천천히 릴리스하고 싶다면, v2의 라우팅 가중치를 10%로 지정할 수 있다.
    • catalog로 향하는 전체 트래픽의 10%는 v2로 가고 90%는 여전히 v1으로 갈 것이다.
    • 이렇게 하면, 전체 트래픽 중 얼마만큼이 v2 코드에 부정적 영향을 받을지 제어함으로써 릴리스의 위험성을 더욱 줄일 수 있다.

    • 다크 런치와 마찬가지로 서비스에 오류가 있는지 모니터링하고 관찰하려고 하며, 문제가 있으면 릴리스를 롤백하려고 한다.
    • 이 경우, 롤백은 catalog v2 서비스로 가는 트래픽 가중치를 줄이는 것만큼(필요한 경우 다시 0%까지) 간단하다.
    • 이스티오로 가중치 기반의 트래픽 전환을 수행하는 방법을 살펴보자.

    • 모든 트래픽을 다시 catalog v1으로 설정
      # 이전 절부터 다음 서비스가 실행 중이다 : 파드에 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로 라우팅
      # mesh route에서 weight 설정 추가 - v1:v2 90:10
      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
      ...


      v2 10% 라우팅 확인



    • 트래픽 50:50 반반 라우팅
      # mesh route weight 설정 변경 - v1:v2 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
      ...

      v1:v2 50%씩 라우팅 확인
    • 각 서비스 버전의 트래픽 가중치를 1에서 100 사이로 바꿀 수 있지만, 가중치 총합은 반드시 100이어야 함
    • 그렇지 않으면 예기치 못한 트래픽 라우팅 발생 가능
    • v1, v2 외 다른 버전이 있을 경우, DestinationRule 에서 subnet 으로 선언해야 한다는 것도 유념
  • 5.3.1 (자동화) flagger(쿠버네티스용 Progressive Delivery Operator)를 사용한 Canary 릴리스
    • 이전 절에서 본 것처럼 Istio는 트래픽 라우팅을 제어하는 강력한 기능을 운영자에게 제공하지만,
      라우팅 변경과 새로운 설정 적용은 CLI에서 수동으로 진행해야 함
    • 또한 여러 버전의 설정을 만들었기 때문에 작업도 더 많았고 설정이 잘못될 가능성도 있음
    • 수백 개의 릴리스가 동시에 진행될 수도 있으므로, 카나리를 수행할 때 사람에 의한 수작업을 피하길 원하며 실수 가능성도 줄이고자 함.
    • Flagger 등을 사용하면 서비스 릴리스를 자동화할 수 있음
      https://github.com/stefanprodan/gitops-istio

      • Flagger는 스테판 프로단(Stefan Prodan) 이 만든 Canary 자동화 도구
      • 릴리스를 어떻게 수행할지, 언제 더 많은 사용자에게 릴리스를 개방할지, 릴리스가 문제를 일으킬 경우 언제 롤백할지 등에 관련된 파라미터를 지정할 수 있음
      • Flagger는 릴리스를 수행하는 데 필요한 작절한 설정을 모두 만들 수 있음

      • Istio 와 Flagger를 함께 사용하는 방법을 알아본다.
      • 이전 절에서는 catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 배포
      • 이 둘을 제거하고 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가 성공 메트릭을 사용하려면 Prometheus를 설치해 Istio Dataplane수집해야 한다.
      • 실습에 따랐다면 Prometheus 샘플이 이미 설치돼 있을 것이다.


      • 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 을 https://jwt.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"
        }


        설치
        jwt.io 에서 토큰 디코드값 확인




      • Flagger Canara 리소스를 사용해 카나리 릴리스의 파라미터를 지정
        : Flagger가 적절한 리로스를 만들어 이 릴리스를 주관 - https://docs.flagger.app/usage/how-it-works

        targetRef: 카나리 대상 Deployment(catalog v1)
        service: 서비스용 설정 (gateway: mesh 에서 동작 처리)
        analysis: 카나리 할 때 필요한 분석에 대해 설정, Prometheus metric을 봄 (success-rate, duration). 만족하지 못하면 롤백

        #  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


        • 45초마다 카나리의 각 단계를 평가하고, 단계별로 트래픽을 10%씩 늘린다. 트래픽이 50%에 도달하면 100%로 바꾼다.
        • 성공률 메트릭의 경우 1분 동안의 성공률이 99% 이상이어야 한다. 또한 P99(상위 99%) 요청 시간은 500ms까지 허용한다.
        • 이 메트릭들이 연속으로 5회를 초과해 지정한 범위와 다르면, 롤백한다.

      • 위 설정을 적용하고 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
        ...


        로그 확인 - initialized

        catalog-primary deployment/service 가 복제되어 생성되어 있음(구버전), 기존 catalog deploy/service 는 파드가 0으로 됨
        카나리는 아직 ENDPOINT가 없음 (신규 버전(v2) 배포하지 않음)



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

      • 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않음
      • Flagger는 원본 디폴로이먼트 대상의 변경 사항을 지켜보고, 카나리 디폴로이먼트(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가 정상 메트릭 기준선을 가져올 수 있도록 Istio를 통해 서비스에 대한 부하 생성
        • 카나리는 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







          진행 간 이슈가 없었으나..
          scale down이 실패해서 canary 실패 함.


          rollback 된 상태




          이를 통해 primary 삭제가 되지 않을 경우 canary 실패하여 롤백한단 사실을 알게 됨.
          primary replicas 종료 불가의 경우 원인 파악 필요 **


      • 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 Traffic Mirroring

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


  • 미러링 방식을 사용하면, 실제 운영 환경 트래픽을 배포로 보냄으로써 사용자에게 영향을 주지 않고 새 코드가 어떻게 동작할지에 대한 실제 피드백을 얻을 수 있다.
  • Istio는 트래픽 미러링을 지원하며, 이는 배포 및 릴리스 수행의 위험성을 다른 두 방식보다 휠씬 더 줄일 수 있다.
  • 미러링 Mirroring vs. 복제 Replication 의 용어의 차이? 동작의 차이는?
    구분 미러링(Mirroring) 복제(Replication)
    목적 고가용성, 장애 복구, 실시간 백업 데이터 분산, 부하 분산, 백업, 확장성
    동작 실시간 또는 거의 실시간으로 동기화 실시간, 주기적, 비동기 등 다양
    일관성 원본과 거의 동일하게 유지 복제본마다 일관성 수준이 다를 수 있음
    활용 예시 스토리지/DB 미러링, 네트워크 포트 미러링 DB 복제, 파일/콘텐츠 복제, CDN
     
  • 실습 환경 초기화
    # 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

  • 미러링 수행이 필요한 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)로 전송하는 이른바 ‘보내고 잊는 방식’으로 수행된다.
    • 미러링된 요청은 실제 요청에는 영향을 줄 수 없는데, 미러링을 수행하는 이스티오 프록시미러링된 클러스터에서 오는 응답을 모두(성공이든, 실패든) 무시해버리기 때문이다.

  • 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
    ...


    catalog istio-proxy 로그 활성화 / proxy-config : webapp, catalog

    v1 app 로그 확인

    v2 app 로그 확인 : 미러링된 트래픽이 catalog v2로 향할때, Host 헤더가 수정돼 미러링/섀도잉된 트래픽임을 나타냄
    Host:catalog:8080 대신 Host:catalog-shadow:8080이 된다.
    -shadow 접미사가 붙은 요청을 받는 서비스는 그 요청이 미러링된 요청임을 식별할 수 있어, 요청을 처리할 때 고려할 수 있다
    (ex. 응답이 버려질 테니 트랜잭션을 롤백하지 않거나 리소스를 많이 사용하는 호출을 하지 않는 것 등)

    route의 cluster는 v1이고 requestMirrorPolicies의 cluster는 v2


    미러링 트래픽 확인 (아이콘: 네모 두개에 화살표 표시)

  • (심화) 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 파드가 catalog v2 미러링 전달
    catalog v2 에서 응답 받음




  • 트래픽 미러링은? 릴리스의 위험성을 낮추는 방법 중 하나.
  • 요청 라우팅 및 트래픽 전환과 마찬가지로, 애플리케이션은 상황을 인지해 실제와 미러링 모드 모두로 동작하거나, 여러 버전으로 동작하거나, 혹은 둘 다 모두 할 수 있어야 함
  • 자세한 내용은 블로그 게시물을 참조

 

 


5.5 Istio의 Service Discovery을 사용하여 클러스터 외부 서비스로 라우팅

  • 들어가며
    • 기본적으로, Istio는 트래픽이 서비스 메시 밖으로 향하는 것을 허용
    • 외부 트래픽이 차단되도록 서비스엔트리 활용 가능
    • 기본 정책을 바꿔, 외부의 데이터가 들어왔다가 응답 처리하는 건 가능하지만 내부환경에서 직접 나가는 건 불가능하도록 설정 가능
      • 기본적으로 어떤 트래픽도 서비스 메시를 떠날 수 없도록 하자
    • Outbound Traffic Policy. 메시수준 통제정책 (ALLOW_ANY)
    • Blackhole Cluster: 라우팅을 보내서 차단
  • 외부 트래픽을 차단하도록 이스티오를 설정해 메시에 간단한 보호 계층 더하기 (그림 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"
                        }
                    },

    현재 istiooperators meshConfig 설정 확인

    webapp pod에서 외부 다운로드
    배포 확인
    webapp pod에서 외부 다운로드 - 차단
    webapp 로그: BlackHoleCluster 차단
    proxy-config : webapp
    Outbound Traffic Policy가 등록된 정보만 접근 가능하도록 변경되었다.
  • ServiceEntry 소개 : Istio의 Service Discovery 기능을 사용해 클러스터 외부의 서비스로 라우팅하기
    • 모든 서비스가 서비스 메시 내에 있는 것은 아니므로, 메시 내부의 서비스가 메시 외부의 서비스와 통신할 방법이 필요하다.
    • 외부에 있는 서비스를 내부에 있는 서비스인 것처럼 Service Discovery에 등록
      저장소에 ServiceEntry 리소스로 외부 서비스를 추가 가능
      ⇒ 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 같은 메서드도 흉내낼 수 있지만 실제 데이터는 저장되지는 않음. (가상의 응답만 리턴)
      • 다음과 같은 리소스 엔드포인트를 제공
        리소스 설명 예시 요청
        /posts 게시글 목록 GET /posts
        /comments 댓글 목록 GET /comments
        /albums 앨범 목록 GET /albums
        /photos 사진 정보 GET /photos
        /todos 할일 목록 GET /todos
        /users 사용자 정보 GET /users
      • 이 ServiceEntry 리소스는 항목을 Istio 서비스 저장소에 삽입하는데, 이는 메시 내 클라이언트가 호스트 jsonplaceholder.typicode.com 를 사용해 JSON 플레이스홀더를 호출할 수 있음을 명시함.
      • JSON 플레이스홀더 서비스는 클러스터 외부에 있는 서비스와의 통신을 실험하는 데 사용할 수 있도록 예제 REST API를 노출함
      • 이 ServiceEntry를 만들기 전에 jsonplaceholder.typicode.com REST API를 호출하는 서비스를 설치하고, Istio가 실제로 밖으로 나가는 모든 트래픽을 막는지 관찰 (실습)
        # 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 을 사용하는 예시 포럼 애플리케이션을 설치
          # 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=forum
          docker exec -it myk8s-control-plane istioctl proxy-status
          
          
          # webapp 웹 접속
          open http://webapp.istioinaction.io:30000/


          forum app 설치

          웹 브라우저에서 http://webapp.istioinaction.io:30000 접속 및 새로 고침
          Kiali에서 트래픽 흐름 확인



        • 메시 안에서 새로운 포럼 서비스 호출
          # 메시 안에서 새로운 포럼 서비스를 호출
          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


          forum 로그 확인: 로그가 찍히지 않았다
          controlplane 안에서도 마찬가지

          • 이 호출을 허용하기 위해 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/


            forum serviceentry 설치

            endpoint address가 jsonplaceholder.typicode.com



            메시 안에서 새로운 포럼 서비스 호출: 사용자 목록 반환
            kiali 확인: jsonplaceholder 로 트래픽 전송 현황 확인

            webapp 웹 접속 시 forum 서비스데이터 정상 확인 됨
            외부 데이터가 istio 서비스로 인식됨 (jsonplaceholder.typicode.com)


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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    •  
    •  

 

 

 

 

 

 

 

 

 

 

Contents

포스팅 주소를 복사했습니다.

이 글이 도움이 되었다면 공감 부탁드립니다.